├── ChangeLog.txt ├── DLConversionV2.psd1 ├── DLConversionV2.psm1 ├── Disable-OriginalDL.ps1 ├── Enable-MailDynamicGroup.ps1 ├── Enable-MailRoutingContact.ps1 ├── Get-ADObjectConfiguration.ps1 ├── Get-AzureADDLConfiguration.ps1 ├── Get-AzureADMembership.ps1 ├── Get-CanonicalName.ps1 ├── Get-GroupSendAsPermission.ps1 ├── Get-MigrationSummary.ps1 ├── Get-NormalizedDN.ps1 ├── Get-NormalizedDNAD.ps1 ├── Get-NormalizedO365.ps1 ├── Get-O365DLConfiguration.ps1 ├── Get-O365DLFullMaiboxAccess.ps1 ├── Get-O365DLMailboxFolderPermissions.ps1 ├── Get-O365DLMembership.ps1 ├── Get-O365DLSendAs.ps1 ├── Get-O365GroupConfiguration.ps1 ├── Get-O365GroupDependency.ps1 ├── Get-msGraphMembership.ps1 ├── Get-onPremFullMailboxAccess.ps1 ├── Get-onPremSendAs.ps1 ├── Import-PowershellSession.ps1 ├── Invoke-ADConnect.ps1 ├── Invoke-ADReplication.ps1 ├── Invoke-Office365SafetyCheck.ps1 ├── New-ExchangeOnlinePowershellSession.ps1 ├── New-LogFile.ps1 ├── New-PowershellSession.ps1 ├── New-StatusFile.ps1 ├── Out-LogFile.ps1 ├── Out-XMLFile.ps1 ├── QuickStartGuide.txt ├── Remove-StringSpace.ps1 ├── Remove-statusFiles.ps1 ├── SampleMigrationScript.txt ├── Set-NewDLName.ps1 ├── Start-ArchiveFiles.ps1 ├── Test-AcceptedDomain.ps1 ├── Test-O365Recipient.ps1 ├── Test-OutboundConnector.ps1 ├── Test-PowershellModule.ps1 ├── Test-PowershellVersion.ps1 ├── compare-recipientArrays.ps1 ├── compare-recipientProperties.ps1 ├── convert-O365DLSettingsToOnPremSettings.ps1 ├── convert-Office365DLtoUnifiedGroup.ps1 ├── disable-allPowerShellSessions.ps1 ├── enable-ExchangeOnPremEntireForest.ps1 ├── enable-hybridMailFlowPostMigration.ps1 ├── get-ActiveDirectoryDomainName.ps1 ├── get-DLHealthReport.ps1 ├── get-ElapsedTime.ps1 ├── get-ExchangeSchemaVersion.ps1 ├── get-OULocation.ps1 ├── get-StatusFileCount.ps1 ├── get-UniversalDateTime.ps1 ├── get-distinguishedName.ps1 ├── get-mailOnMicrosoftComDomain.ps1 ├── get-msGraphDLConfiguration.ps1 ├── get-multipleDLHealthReports.ps1 ├── get-onPremFolderPermissions.ps1 ├── license.md ├── move-toNonSyncOU.ps1 ├── new-AzureADPowershellSession.ps1 ├── new-MSGraphPowershellSession.ps1 ├── new-Office365DL.ps1 ├── new-Office365Group.ps1 ├── new-RoutingContact.ps1 ├── out-StatusFile.ps1 ├── readme.md ├── remove-groupViaGraph.ps1 ├── remove-o365CloudOnlyGroup.ps1 ├── remove-onPremGroup.ps1 ├── restore-MigratedDistributionList.ps1 ├── send-TelemetryEvent.ps1 ├── set-Office365DL.ps1 ├── set-Office365DLMV.ps1 ├── set-Office365DLPermissions.ps1 ├── set-Office365Group.ps1 ├── set-Office365GroupMV.ps1 ├── set-OnPremDLPermissions.ps1 ├── show-QuickStartGuide.ps1 ├── show-SampleMigrationScript.ps1 ├── start-MultipleDistributionListMigration.ps1 ├── start-MultipleMachineDistributionListMigration.ps1 ├── start-Office365GroupMigration.ps1 ├── start-ReplaceOnPrem.ps1 ├── start-ReplaceOnPremSV.ps1 ├── start-collectOffice365FullMailboxAccess.ps1 ├── start-collectOffice365MailboxFolders.ps1 ├── start-collectOnPremFullMailboxAccess.ps1 ├── start-collectOnPremMailboxFolders.ps1 ├── start-collectOnPremSendAs.ps1 ├── start-multipleTestPreMigrations.ps1 ├── start-multipleTestPreOffice365Group.ps1 ├── start-parameterValidation.ps1 ├── start-replaceOffice365.ps1 ├── start-replaceOffice365Dynamic.ps1 ├── start-replaceOffice365Members.ps1 ├── start-replaceOffice365Unified.ps1 ├── start-sleepProgress.ps1 ├── start-telemetryConfiguration.ps1 ├── start-testO365UnifiedGroupDependency.ps1 ├── start-upgradeToOffice365Group.ps1 ├── test-CloudDLPresent.ps1 ├── test-PreMigration.ps1 ├── test-PreMigrationO365Group.ps1 ├── test-credentials.ps1 ├── test-dlNameLength.ps1 ├── test-itemCount.ps1 ├── test-nonSyncDL.ps1 ├── test-nonSyncOU.ps1 ├── test-preO365GroupConversion.ps1 ├── update-hybridMailAddress.ps1 ├── write-FunctionParameters.ps1 ├── write-errorEntry.ps1 ├── write-hashtable.ps1 └── write-shamelessPlug.ps1 /ChangeLog.txt: -------------------------------------------------------------------------------- 1 | ================================================ 2 | Issue #161 - fixed in build 2.9.8.31 3 | 4/10/2024 4 | 5 | Performed spell check against all out-logFile entries and other string values. Corrections made. 6 | Corrected extension attribute names in PSObjects when extension attribute failures occur. 7 | ================================================ 8 | 9 | ================================================ 10 | Issue #166 - fixed in build 2.9.8.32 11 | 4/15/2024 12 | 13 | Created function restore-MigratedDistributionList 14 | 15 | This function takes the originalDLConfigurationADXML and imports the settings. A restoration of the group based on this data is then performed. 16 | 17 | If the group mail address is found (most likely the dynamic DL) the administrator is prompted to remove it. (Mandatory) 18 | 19 | If the group mail address-migratedBySCript is found the administrator is prompted to remove it. (Optional) 20 | 21 | If the renamed group with ! is found by searching for object GUID then a restoration of attributes is performed to this object. 22 | 23 | If the renamed group via objectGUID is not found then an entirely new group is created. 24 | 25 | Graph is utilized to delete the group based on group SID from Entra ID. This allows ad connect to soft match the restored group. 26 | 27 | NOTE: This restores the group to it's state at the time of migration and will result in rolling back any changes in Office 365. 28 | ================================================ 29 | 30 | ================================================ 31 | Issue #164 - addressed in build 2.9.8.33 32 | 4/15/2024 33 | 34 | Customer migrating from machine that is Azure AD Joined and not a member of the domain. 35 | 36 | Multiple ad calls failing. 37 | 38 | Discovered that all AD functions were not implementing the auth type specified in the DL migration command. 39 | 40 | Updated the acceptable types from Basic,Kerberos to Basic,Negotiate. Using negotiate allows NTML or Kerberos. 41 | 42 | Updated all functions that utilize AD commands to include the parameter $activeDirectoryAuthenticationMethod. 43 | 44 | Updated all function calls to pass the method. 45 | 46 | Default is negotiate. 47 | ================================================ 48 | 49 | ================================================ 50 | 2.9.8.34 51 | 4/17/2024 52 | 53 | Implemented functions show-QuickStartGuid and show-SampelMigrationScript to provide quick start information to users. 54 | ================================================ -------------------------------------------------------------------------------- /DLConversionV2.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timmcmic/DLConversionV2/9f2fcd7fb3bdcfe0daefff05d639a6b169e16a5d/DLConversionV2.psd1 -------------------------------------------------------------------------------- /Disable-OriginalDL.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function disabled the on premies distribution list - removing it from azure ad and exchange online. 5 | 6 | .DESCRIPTION 7 | 8 | This function disabled the on premies distribution list - removing it from azure ad and exchange online. 9 | 10 | .PARAMETER parameterSet 11 | 12 | These are the parameters that will be manually cleared from the object in AD mode. 13 | 14 | .PARAMETER DN 15 | 16 | The DN of the group to remove. 17 | 18 | .PARAMETER GlobalCatalog 19 | 20 | The global catalog server the operation should be performed on. 21 | 22 | .PARAMETER UseOnPremisesExchange 23 | 24 | If set to true disablement will occur using the exchange on premises powershell commands. 25 | 26 | .PARAMETER adCredential 27 | 28 | The active directory credential for AD operations. 29 | 30 | .OUTPUTS 31 | 32 | No return. 33 | 34 | .EXAMPLE 35 | 36 | Disable-OriginalDL -originalDLConfiguration $configuration -globalCatalogServer $GC -parameterSet $parameterArray -adCredential $cred 37 | 38 | #> 39 | Function Disable-OriginalDL 40 | { 41 | [cmdletbinding()] 42 | 43 | Param 44 | ( 45 | [Parameter(Mandatory = $true)] 46 | $originalDLConfiguration, 47 | [Parameter(Mandatory = $true)] 48 | [string]$globalCatalogServer, 49 | [Parameter(Mandatory = $false)] 50 | [array]$parameterSet="None", 51 | [Parameter(Mandatory = $false)] 52 | [boolean]$useOnPremisesExchange=$FALSE, 53 | [Parameter(Mandatory = $true)] 54 | $adCredential, 55 | [Parameter(Mandatory = $false)] 56 | [ValidateSet("Basic","Negotiate")] 57 | $activeDirectoryAuthenticationMethod="Negotiate" 58 | ) 59 | 60 | #Output all parameters bound or unbound and their associated values. 61 | 62 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 63 | 64 | #Declare function variables. 65 | 66 | $functionDLConfiguration=$NULL #Holds the return information for the group query. 67 | [string]$functionCustomAttribute1="MigratedByScript" 68 | [string]$functionCustomAttribute2=$originalDLConfiguration.mail 69 | 70 | #Start function processing. 71 | 72 | Out-LogFile -string "********************************************************************************" 73 | Out-LogFile -string "BEGIN Disable-OriginalDLConfiguration" 74 | Out-LogFile -string "********************************************************************************" 75 | 76 | out-logfile -string ("Disabled DL Custom Attribute 1 = "+$functionCustomAttribute1) 77 | out-logfile -string ("Disabled DL Custom Attribute 2 = "+$functionCustomAttribute2) 78 | 79 | #Get the group using LDAP / AD providers. 80 | 81 | try 82 | { 83 | set-adgroup -identity $originalDLConfiguration.distinguishedName -server $globalCatalogServer -clear $parameterSet -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction Stop 84 | 85 | } 86 | catch 87 | { 88 | out-logfile -string "Unable to mail disable the original distribution group. Failing entire job - manual cleanup required - hybrid mail flow cannot proceed." 89 | Out-LogFile -string $_ -isError:$TRUE 90 | } 91 | 92 | #Now that the DL is disabled - use this oppurtunity to write the custom attributes to show it's been migrated. 93 | 94 | out-logfile -string "The group has been migrated and is retained - set custom attributes with original information for other migration dependencies." 95 | 96 | try { 97 | set-adgroup -identity $originalDLConfiguration.distinguishedName -add @{extensionAttribute1=$functionCustomAttribute1;extensionAttribute2=$functionCustomAttribute2} -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 98 | } 99 | catch { 100 | out-logfile -string $_ -isError:$TRUE 101 | } 102 | 103 | Out-LogFile -string "END Disable-OriginalDLConfiguration" 104 | Out-LogFile -string "********************************************************************************" 105 | } -------------------------------------------------------------------------------- /Get-AzureADDLConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function gathers the group information from Azure Active Directory. 5 | 6 | .DESCRIPTION 7 | 8 | This function gathers the group information from Azure Active Directory. 9 | 10 | .PARAMETER office365DLConfiguration 11 | 12 | The Office 365 DL configuration for the group. 13 | 14 | .OUTPUTS 15 | 16 | Returns the information from the associated group from Azure AD> 17 | 18 | .EXAMPLE 19 | 20 | get-AzureADDLConfiguration -office365DLConfiguration $configuration 21 | 22 | #> 23 | Function Get-AzureADDLConfiguration 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | $office365DLConfiguration 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | #Start function processing. 38 | 39 | Out-LogFile -string "********************************************************************************" 40 | Out-LogFile -string "BEGIN GET-AZUREADDLCONFIGURATION" 41 | Out-LogFile -string "********************************************************************************" 42 | 43 | #Get the recipient using the exchange online powershell session. 44 | 45 | try{ 46 | $functionDLConfiguration = get-AzureADGroup -objectID $office365DLConfiguration.externalDirectoryObjectID -errorAction STOP 47 | } 48 | catch { 49 | out-logfile -string $_ 50 | out-logfile -string "Unable to obtain group configuration from Azure Active Directory" 51 | } 52 | 53 | Out-LogFile -string "END GET-AzureADDlConfiguration" 54 | Out-LogFile -string "********************************************************************************" 55 | 56 | #This function is designed to open local and remote powershell sessions. 57 | #If the session requires import - for example exchange - return the session for later work. 58 | #If not no return is required. 59 | 60 | return $functionDLConfiguration 61 | } -------------------------------------------------------------------------------- /Get-AzureADMembership.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function obtains the DL membership of the Office 365 distribution group. 5 | 6 | .DESCRIPTION 7 | 8 | This function obtains the DL membership of the Office 365 distribution group. 9 | 10 | .PARAMETER GroupObjectID 11 | 12 | The Object ID to obtain membership values from Azure. 13 | 14 | .OUTPUTS 15 | 16 | Returns the membership array of the DL in Office 365. 17 | 18 | .EXAMPLE 19 | 20 | get-o365dlMembership -groupSMTPAddress Address 21 | 22 | #> 23 | Function Get-AzureADMembership 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupObjectID, 31 | [Parameter(Mandatory = $false)] 32 | [boolean]$isHealthReport=$false 33 | ) 34 | 35 | #Output all parameters bound or unbound and their associated values. 36 | 37 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 38 | 39 | #Declare function variables. 40 | 41 | $functionDLMembership=$NULL #Holds the return information for the group query. 42 | 43 | #Start function processing. 44 | 45 | Out-LogFile -string "********************************************************************************" 46 | Out-LogFile -string "BEGIN GET-AZUREADMEMBERSHIP" 47 | Out-LogFile -string "********************************************************************************" 48 | 49 | #Get the recipient using the exchange online powershell session. 50 | 51 | out-logfile -string "Attempting to obtain the Azure AD Group membership." 52 | 53 | if ($isHealthReport -eq $FALSE) 54 | { 55 | try { 56 | $functionDLMembership = get-azureADGroupMember -objectID $groupobjectID -all:$TRUE -errorAction STOP 57 | } 58 | catch { 59 | out-logfile -string "Unable to obtain the azure group membership." 60 | out-logfile -string $_ -isError:$TRUE 61 | } 62 | } 63 | else 64 | { 65 | try { 66 | $functionDLMembership = get-azureADGroupMember -objectID $groupobjectID -all:$TRUE -errorAction STOP | select-object objectID,objectType,mail,mailnickname,onPremisesSecurityIdentifier,proxyAddresses,userPrincipalName,userType,provisioningErrors 67 | } 68 | catch { 69 | out-logfile -string "Unable to obtain the azure group membership." 70 | out-logfile -string $_ -isError:$TRUE 71 | } 72 | } 73 | 74 | 75 | 76 | if ($functionDLMembership.count -gt 0) 77 | { 78 | out-logfile -string $functionDLMembership 79 | } 80 | else 81 | { 82 | out-logfile -string "No Azure AD Group members in the specified group." 83 | } 84 | 85 | Out-LogFile -string "END GET-AZUREADMEMBERSHIP" 86 | Out-LogFile -string "********************************************************************************" 87 | 88 | #Return the membership to the caller. 89 | 90 | return $functionDLMembership 91 | } -------------------------------------------------------------------------------- /Get-CanonicalName.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function returns the canonicalName associated with a distinguished name. 5 | 6 | .DESCRIPTION 7 | 8 | This function returns the canonicalName associated with a distinguished name. 9 | 10 | .PARAMETER GlobalCatalog 11 | 12 | The global catalog to make the query against. 13 | 14 | .PARAMETER DN 15 | 16 | The DN of the object to pass to normalize. 17 | 18 | .OUTPUTS 19 | 20 | The canonical name of a given object. 21 | 22 | .EXAMPLE 23 | 24 | get-canonicalName -globalCatalog GC -DN DN -adCredential $cred 25 | 26 | #> 27 | Function get-canonicalName 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | [string]$globalCatalogServer, 35 | [Parameter(Mandatory = $true)] 36 | [string]$DN, 37 | [Parameter(Mandatory = $false)] 38 | [ValidateSet("Basic","Negotiate")] 39 | $activeDirectoryAuthenticationMethod="Negotiate", 40 | [Parameter(Mandatory = $true)] 41 | $adCredential 42 | ) 43 | 44 | #Output all parameters bound or unbound and their associated values. 45 | 46 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 47 | 48 | #Declare function variables. 49 | 50 | $functionTest=$NULL #Holds the return information for the group query. 51 | $functionObject=$NULL #This is used to hold the object that will be returned. 52 | $functionDomain=$NULL 53 | 54 | #Start function processing. 55 | 56 | Out-LogFile -string "********************************************************************************" 57 | Out-LogFile -string "BEGIN GET-CanoicalName" 58 | Out-LogFile -string "********************************************************************************" 59 | 60 | #Get the specific user using ad providers. 61 | 62 | $stopLoop = $FALSE 63 | [int]$loopCounter = 0 64 | 65 | do { 66 | try 67 | { 68 | Out-LogFile -string "Gathering the AD object based on distinguished name." 69 | 70 | $functionTest = get-adobject -filter {distinguishedname -eq $dn} -properties canonicalName -credential $adCredential -authType $activeDirectoryAuthenticationMethod -server $globalCatalogServer -errorAction STOP 71 | 72 | if ($functionTest.canonicalName -ne $NULL) 73 | { 74 | out-logfile -string "The object was successfully located and has a canonical name." 75 | out-logfile -string $functionTest.canonicalName 76 | $stopLoop = $TRUE 77 | } 78 | else 79 | { 80 | out-logfile -string "Object not located by ad referral - attempting call with no GC information." 81 | 82 | $functionTest = get-adobject -filter {distinguishedname -eq $dn} -properties canonicalName -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 83 | 84 | if ($functionTest.caonicalName -eq $NULL) 85 | { 86 | out-logfile -string "Object was still not located by ad referral - error." 87 | out-logfile -string "Error locating object for canonical name discovery." -isError:$TRUE 88 | } 89 | else 90 | { 91 | out-logfile -string "The object was successfully located and has a canonical name." 92 | out-logfile -string $functionTest.canonicalName 93 | $stopLoop = $TRUE 94 | } 95 | } 96 | } 97 | catch 98 | { 99 | if ($loopCounter -gt 4) 100 | { 101 | out-logfile -string $_ -isError:$TRUE 102 | } 103 | else 104 | { 105 | out-logfile -string "Error getting AD object - sleep and retry." 106 | 107 | $loopCounter = $loopCounter +1 108 | 109 | start-sleepProgress -sleepString "Error with get-adobject -> sleep and try again." -sleepSeconds 5 110 | 111 | } 112 | } 113 | 114 | } until ($stopLoop -eq $TRUE) 115 | 116 | 117 | try 118 | { 119 | #Now that we have the canonicalName - record it and build just the domain name portion of it for reference. 120 | 121 | #Split the string at / -> results in the domain name being in position 0. 122 | 123 | $functionDomain=$functiontest.canonicalName.split("/") 124 | 125 | $functionObject = New-Object PSObject -Property @{ 126 | canonicalName = $functionTest.canonicalName 127 | canonicalDomainName = $functionDomain[0] 128 | distinguishedName = $functiontest.distinguishedName 129 | } 130 | } 131 | catch 132 | { 133 | Out-LogFile -string $_ -isError:$true 134 | } 135 | 136 | Out-LogFile -string "END GET-CanonicalName" 137 | Out-LogFile -string "********************************************************************************" 138 | 139 | #This function is designed to open local and remote powershell sessions. 140 | #If the session requires import - for example exchange - return the session for later work. 141 | #If not no return is required. 142 | 143 | return $functionObject 144 | } -------------------------------------------------------------------------------- /Get-MigrationSummary.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function scans the folders and outputs a migration summary. 5 | 6 | .DESCRIPTION 7 | 8 | This function scans the folders and outputs a migration summary. 9 | 10 | .PARAMETER logFolderPath 11 | 12 | This is the log folder path that contains the files for auditing. 13 | 14 | .OUTPUTS 15 | 16 | No return. 17 | 18 | .EXAMPLE 19 | 20 | get-MigrationSummary -logFolderPath $logFolderPath 21 | 22 | #> 23 | Function get-MigrationSummary 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | $logFolderPath 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | [array]$failedJobs = @() 38 | [array]$successJobs = @() 39 | $workingDirectories = $NULL 40 | 41 | #Start function processing. 42 | 43 | Out-LogFile -string '********************************************************************************' 44 | Out-LogFile -string 'BEGIN GET-MIGRATIONSUMMARY' 45 | Out-LogFile -string '********************************************************************************' 46 | 47 | $workingDirectories = get-childItem -path $logFolderPath -recurse -directory 48 | 49 | foreach ($directory in $workingDirectories) 50 | { 51 | out-logfile -string ('Evaluating Directory: '+$directory.name) 52 | 53 | if ($directory.name -like '*FAILED') 54 | { 55 | out-logfile -string ('Failed Detected: '+$directory.name) 56 | $failedJobs+=$directory 57 | } 58 | elseif ($directory.name -like '*SUCCESS*') 59 | { 60 | out-logfile -string ('Success Detected: '+$directory.name) 61 | $successJobs+=$directory 62 | } 63 | else 64 | { 65 | out-logfile -string 'Directory neither success or fail.' 66 | } 67 | } 68 | 69 | out-logfile -string '================================================================================' 70 | out-logfile -string '***MIGRATION SUMMARY***' 71 | out-logfile -string '================================================================================' 72 | out-logfile -string '' 73 | out-logfile -string '+++FAILED SUMMARY+++' 74 | out-logfile -string ('Number of failed migrations: ' + $failedJobs.count) 75 | 76 | foreach ($job in $failedJobs) 77 | { 78 | $temp = $job.name.split('-') 79 | $tempStatus = $temp[-1] 80 | $tempGroupName = $temp[1..($temp.count - 2)] -join '-' 81 | out-logfile -string "Group: $($tempGroupName) Status: $($tempStatus)" 82 | } 83 | 84 | out-logfile -string '' 85 | out-logfile -string '+++SUCCESS SUMMARY+++' 86 | out-logfile -string ('Number of successful migrations: ' + $successJobs.count) 87 | 88 | foreach ($job in $successJobs) 89 | { 90 | $temp = $job.name.split('-') 91 | $tempStatus = $temp[-1] 92 | $tempGroupName = $temp[1..($temp.count - 2)] -join '-' 93 | out-logfile -string "Group: $($tempGroupName) Status: $($tempStatus)" 94 | } 95 | 96 | out-logfile -string '================================================================================' 97 | out-logfile -string '================================================================================' 98 | 99 | 100 | Out-LogFile -string 'END GET-MIGRATIONSUMMARY' 101 | Out-LogFile -string '********************************************************************************' 102 | } 103 | -------------------------------------------------------------------------------- /Get-O365DLMailboxFolderPermissions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function evaluates all Office 365 Mailbox Folder permissions for the migrated distriution groups. 5 | 6 | .DESCRIPTION 7 | 8 | This function evaluates all Office 365 Mailbox Folder permissions for the migrated distriution groups. 9 | 10 | .PARAMETER GroupSMTPAddress 11 | 12 | The mail attribute of the group to search. 13 | 14 | .PARAMETER collectedData 15 | 16 | The precollected data to search for the group. 17 | 18 | .OUTPUTS 19 | 20 | Returns any mailbox folder permissions for the migrated DL. 21 | 22 | .EXAMPLE 23 | 24 | Get-O365DLFullMaiboxAccess -groupSMTPAddress Address 25 | 26 | #> 27 | Function Get-O365DLMailboxFolderPermissions 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | [string]$groupSMTPAddress, 35 | [Parameter(Mandatory = $false)] 36 | $collectedData=$NULL 37 | ) 38 | 39 | #Output all parameters bound or unbound and their associated values. 40 | 41 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 42 | 43 | #Declare function variables. 44 | 45 | [array]$functionFolderAccess=@() 46 | 47 | #Start function processing. 48 | 49 | Out-LogFile -string "********************************************************************************" 50 | Out-LogFile -string "BEGIN Get-O365DLMailboxFolderPermissions" 51 | Out-LogFile -string "********************************************************************************" 52 | 53 | #Get the recipient using the exchange online powershell session. 54 | 55 | if ($collectedData -eq $NULL) 56 | { 57 | out-logfile -string "No folder permissions were provided for evaluation." 58 | } 59 | elseif ($collectedData -ne $NULL) 60 | { 61 | <# 62 | out-logfile -string "Filter all entries for objects that have been removed." 63 | out-logfile -string ("Pre count: "+$collectedData.count) 64 | 65 | $collectedData = $collectedData | where {$_.user.userPrincipalName -ne $NULL} 66 | 67 | out-logfile -string ("Post count: "+$collectedData.count) 68 | 69 | $functionFolderAccess = $collectedData | where {$_.user.tostring() -eq $groupSMTPAddress} 70 | #> 71 | 72 | #Moving back from the EXO function changes the output of the gathered permissions. 73 | #This new code section should address it. 74 | #Retaining the original in case it becomes necessary to revert. 75 | 76 | out-logfile -string "Filter all entries for objects that have been removed." 77 | out-logfile -string ("Pre count: "+$collectedData.count) 78 | 79 | $collectedData = $collectedData | where {$_.user.RecipientPrincipal.primarySMTPAddress -ne $NULL} 80 | 81 | out-logfile -string ("Post count: "+$collectedData.count) 82 | 83 | $functionFolderAccess = $collectedData | where {$_.user.RecipientPrincipal.primarySMTPAddress.tostring() -eq $groupSMTPAddress} 84 | } 85 | 86 | write-progress -activity "Processing Recipient" -completed 87 | 88 | Out-LogFile -string "END Get-O365DLMailboxFolderPermissions" 89 | Out-LogFile -string "********************************************************************************" 90 | 91 | if ($functionFolderAccess.count -gt 0) 92 | { 93 | return $functionFolderAccess 94 | } 95 | } -------------------------------------------------------------------------------- /Get-O365DLMembership.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function obtains the DL membership of the Office 365 distribution group. 5 | 6 | .DESCRIPTION 7 | 8 | This function obtains the DL membership of the Office 365 distribution group. 9 | 10 | .PARAMETER GroupSMTPAddress 11 | 12 | The mail attribute of the group to search. 13 | 14 | .OUTPUTS 15 | 16 | Returns the membership array of the DL in Office 365. 17 | 18 | .EXAMPLE 19 | 20 | get-o365dlMembership -groupSMTPAddress Address 21 | 22 | #> 23 | Function Get-o365DLMembership 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupSMTPAddress, 31 | [Parameter(Mandatory = $false)] 32 | [boolean]$isUnifiedGroup=$false, 33 | [Parameter(Mandatory = $false)] 34 | [boolean]$getUnifiedMembers=$false, 35 | [Parameter(Mandatory = $false)] 36 | [boolean]$getUnifiedOwners=$false, 37 | [Parameter(Mandatory = $false)] 38 | [boolean]$getUnifiedSubscribers=$false, 39 | [Parameter(Mandatory = $false)] 40 | [boolean]$isHealthReport=$false 41 | ) 42 | 43 | #Output all parameters bound or unbound and their associated values. 44 | 45 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 46 | 47 | #Declare function variables. 48 | 49 | $functionDLMembership=$NULL #Holds the return information for the group query. 50 | $functionMembersLinkType = "Members" 51 | $functionOwnersLinkType = "Owners" 52 | $functionSubscribersLinkType = "Subscribers" 53 | 54 | #Start function processing. 55 | 56 | Out-LogFile -string "********************************************************************************" 57 | Out-LogFile -string "BEGIN GET-O365DLMEMBERSHIP" 58 | Out-LogFile -string "********************************************************************************" 59 | 60 | #Get the recipient using the exchange online powershell session. 61 | 62 | if ($isUnifiedGroup -eq $FALSE) 63 | { 64 | if ($isHealthReport -eq $FALSE) 65 | { 66 | Out-LogFile -string "Using Exchange Online to obtain the group membership." 67 | 68 | $functionDLMembership=@(get-O365DistributionGroupMember -identity $groupSMTPAddress -resultsize unlimited -errorAction STOP) 69 | 70 | Out-LogFile -string "Distribution group membership recorded." 71 | } 72 | else 73 | { 74 | Out-LogFile -string "Using Exchange Online to obtain the group membership." 75 | 76 | $functionDLMembership=@(get-O365DistributionGroupMember -identity $groupSMTPAddress -resultsize unlimited -errorAction STOP | select-object Identity,Name,Alias,ExternalDirectoryObjectId,EmailAddresses,PrimarySMTPAddress,ExternalEmailAddress,DisplayName,RecipientType,RecipientTypeDetails,ExchangeGuid) 77 | 78 | Out-LogFile -string "Distribution group membership recorded." 79 | } 80 | } 81 | else 82 | { 83 | out-logfile -string "Using Exchange Online to obtain unified group member properties." 84 | 85 | if ($getUnifiedMembers -eq $TRUE) 86 | { 87 | Out-LogFile -string "Using Exchange Online to obtain the unified group membership membership." 88 | 89 | $functionDLMembership=@(get-O365UnifiedGroupLinks -identity $groupSMTPAddress -linkType $functionMembersLinkType -resultsize unlimited -errorAction STOP) 90 | 91 | Out-LogFile -string "Distribution group membership recorded." 92 | } 93 | else 94 | { 95 | out-logfile -string "Call is not for unified group members." 96 | } 97 | 98 | if ($getUnifiedOwners -eq $TRUE) 99 | { 100 | Out-LogFile -string "Using Exchange Online to obtain the unified group owners membership." 101 | 102 | $functionDLMembership=@(get-O365UnifiedGroupLinks -identity $groupSMTPAddress -linkType $functionOwnersLinkType -resultsize unlimited -errorAction STOP) 103 | 104 | Out-LogFile -string "Distribution group owners recorded." 105 | } 106 | else 107 | { 108 | out-logfile -string "Call is not for unified group owners." 109 | } 110 | 111 | if ($getUnifiedSubscribers -eq $TRUE) 112 | { 113 | Out-LogFile -string "Using Exchange Online to obtain the unified group subscribers membership." 114 | 115 | $functionDLMembership=@(get-O365UnifiedGroupLinks -identity $groupSMTPAddress -linkType $functionSubscribersLinkType -resultsize unlimited -errorAction STOP) 116 | 117 | Out-LogFile -string "Distribution group subscribers recorded." 118 | } 119 | else 120 | { 121 | out-logfile -string "Call is not for unified group subscribers." 122 | } 123 | } 124 | 125 | Out-LogFile -string "END GET-O365DLMEMBERSHIP" 126 | Out-LogFile -string "********************************************************************************" 127 | 128 | #Return the membership to the caller. 129 | 130 | return $functionDLMembership 131 | } -------------------------------------------------------------------------------- /Get-O365DLSendAs.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function gathers all Office 365 Send as permissions for the migrated DLs. 5 | 6 | .DESCRIPTION 7 | 8 | This function gathers all Office 365 Send as permissions for the migrated DLs. 9 | 10 | .PARAMETER GroupSMTPAddress 11 | 12 | The mail attribute of the group to search. 13 | 14 | .PARAMETER isTrustee 15 | 16 | Determines if we're searching for permissions on the group itself or permissions for the migrated DL on other objects. 17 | 18 | .OUTPUTS 19 | 20 | Returns either send as permissions on the migrated DL or all objects that the migrated DL has send as permissions on. 21 | 22 | .EXAMPLE 23 | 24 | Get-O365DLSendAs -groupSMTPAddress Address -isTrustee:$TRUE 25 | 26 | #> 27 | Function Get-O365DLSendAs 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | [string]$groupSMTPAddress, 35 | [Parameter(Mandatory = $false)] 36 | [string]$isTrustee=$FALSE, 37 | [Parameter(Mandatory = $false)] 38 | $office365GroupConfiguration 39 | ) 40 | 41 | #Output all parameters bound or unbound and their associated values. 42 | 43 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 44 | 45 | #Declare function variables. 46 | 47 | [array]$functionSendAs=@() 48 | 49 | #Start function processing. 50 | 51 | Out-LogFile -string "********************************************************************************" 52 | Out-LogFile -string "BEGIN Get-O365DLSendAs" 53 | Out-LogFile -string "********************************************************************************" 54 | 55 | #Get the recipient using the exchange online powershell session. 56 | 57 | if ($isTrustee -eq $TRUE) 58 | { 59 | try 60 | { 61 | Out-LogFile -string "Obtaining all Office 365 groups the migrated DL has send as permissions on." 62 | 63 | $functionSendAs = get-o365RecipientPermission -Trustee $groupSMTPAddress -resultsize unlimited -errorAction STOP 64 | } 65 | catch 66 | { 67 | Out-LogFile -string $_ -isError:$TRUE 68 | } 69 | 70 | <# 71 | 72 | out-logfile -string "Determining if the group has permissions to itself and excluding it from the array." 73 | out-logfile -string ("PreCount: "+$functionSendAs.Count) 74 | 75 | $functionSendAs = $functionSendAs | where {$_.TrusteeSidString -ne $office365GroupConfiguration.SID} 76 | 77 | out-logfile -string ("PostCount: "+$functionSendAs.Count) 78 | 79 | #> 80 | } 81 | else 82 | { 83 | try 84 | { 85 | out-logfile -string "Obtaining all send as permissions set directly in Office 365 on the group to be migrated." 86 | 87 | $functionSendAs = get-O365RecipientPermission -identity $groupSMTPAddress -resultsize unlimited -errorAction STOP 88 | 89 | out-logfile -string ("Number of send as located: "+$functionSendAs.Count) 90 | } 91 | catch 92 | { 93 | out-logfile -string $_ -isError:$TRUE 94 | } 95 | } 96 | 97 | Out-LogFile -string "END Get-O365DLSendAs" 98 | Out-LogFile -string "********************************************************************************" 99 | 100 | return $functionSendAs 101 | } -------------------------------------------------------------------------------- /Get-O365GroupConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function uses the exchange online powershell session to gather the office 365 distribution list configuration. 5 | 6 | .DESCRIPTION 7 | 8 | This function uses the exchange online powershell session to gather the office 365 distribution list configuration. 9 | 10 | .PARAMETER GroupSMTPAddress 11 | 12 | The mail attribute of the group to search. 13 | 14 | .OUTPUTS 15 | 16 | Returns the PS object associated with the recipient from get-o365recipient 17 | 18 | .EXAMPLE 19 | 20 | get-o365dlconfiguration -groupSMTPAddress Address -groupTypeOverride 21 | 22 | #> 23 | Function Get-o365GroupConfiguration 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupSMTPAddress 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | #Declare function variables. 38 | 39 | $functionDLConfiguration=$NULL #Holds the return information for the group query. 40 | $functionMailSecurity="MailUniversalSecurityGroup" 41 | $functionMailDistribution="MailUniversalDistributionGroup" 42 | $functionGroupType = "GroupMailbox" 43 | 44 | #Start function processing. 45 | 46 | Out-LogFile -string "********************************************************************************" 47 | Out-LogFile -string "BEGIN GET-O365GroupCONFIGURATION" 48 | Out-LogFile -string "********************************************************************************" 49 | 50 | #Get the recipient using the exchange online powershell session. 51 | 52 | try 53 | { 54 | out-logfile -string "Obtaining Office 365 DL Configuration for evaluation." 55 | 56 | $functionRecipient = get-o365Group -identity $groupSMTPAddress -errorAction STOP 57 | 58 | out-logfile -string "Successfully obtained the Office 365 DL Configuration." 59 | } 60 | catch 61 | { 62 | out-logfile -string "Unable to obtain the Office 365 DL Configuration." 63 | out-logfile -string $_ -isError:$TRUE 64 | } 65 | 66 | out-logfile -string $functionRecipient 67 | 68 | Out-LogFile -string "END GET-O365GroupCONFIGURATION" 69 | Out-LogFile -string "********************************************************************************" 70 | 71 | #This function is designed to open local and remote powershell sessions. 72 | #If the session requires import - for example exchange - return the session for later work. 73 | #If not no return is required. 74 | 75 | return $functionRecipient 76 | } -------------------------------------------------------------------------------- /Get-msGraphMembership.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function obtains the DL membership of the Office 365 distribution group. 5 | 6 | .DESCRIPTION 7 | 8 | This function obtains the DL membership of the Office 365 distribution group. 9 | 10 | .PARAMETER GroupObjectID 11 | 12 | The Object ID to obtain membership values from Azure. 13 | 14 | .OUTPUTS 15 | 16 | Returns the membership array of the DL in Office 365. 17 | 18 | .EXAMPLE 19 | 20 | get-o365dlMembership -groupSMTPAddress Address 21 | 22 | #> 23 | Function Get-msGraphMembership 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupObjectID, 31 | [Parameter(Mandatory = $false)] 32 | [boolean]$isHealthReport=$false 33 | ) 34 | 35 | #Output all parameters bound or unbound and their associated values. 36 | 37 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 38 | 39 | #Declare function variables. 40 | 41 | $functionDLMembership=$NULL #Holds the return information for the group query. 42 | 43 | #Start function processing. 44 | 45 | Out-LogFile -string "********************************************************************************" 46 | Out-LogFile -string "BEGIN GET-msGraphMembership" 47 | Out-LogFile -string "********************************************************************************" 48 | 49 | #Get the recipient using the exchange online powershell session. 50 | 51 | out-logfile -string "Attempting to obtain the MSgraph Group membership." 52 | 53 | try { 54 | $functionDLMembership = get-mgGroupMember -groupID $groupObjectID -all -errorAction STOP 55 | } 56 | catch { 57 | out-logfile -string "Unable to obtain the azure group membership." 58 | out-logfile -string $_ -isError:$TRUE 59 | } 60 | 61 | if ($functionDLMembership.count -gt 0) 62 | { 63 | out-logfile -string $functionDLMembership 64 | } 65 | else 66 | { 67 | out-logfile -string "No Azure AD Group members in the specified group." 68 | } 69 | 70 | Out-LogFile -string "END GET-MSGRAPHMEMBERSHIP" 71 | Out-LogFile -string "********************************************************************************" 72 | 73 | #Return the membership to the caller. 74 | 75 | return $functionDLMembership 76 | } -------------------------------------------------------------------------------- /Get-onPremFullMailboxAccess.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function locates any mailbox level permissions on the DL to be migrated. 5 | 6 | .DESCRIPTION 7 | 8 | This function locates any mailbox level permissions on the DL to be migrated. 9 | 10 | .PARAMETER originalDLConfiguration 11 | 12 | The mail attribute of the group to search. 13 | 14 | .PARAMETER collectedData 15 | 16 | The precollected data to search for full mailbox access permissions. 17 | 18 | .OUTPUTS 19 | 20 | Returns a list of all mailboxes where the migrated DL has full mailbox accesses. 21 | 22 | .EXAMPLE 23 | 24 | Get-onPremFullMailboxAccess -originalDLConfiguration DLConfig -collectedData Data 25 | 26 | #> 27 | Function Get-onPremFullMailboxAccess 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | $originalDLConfiguration, 35 | [Parameter(Mandatory = $false)] 36 | $collectedData=$NULL 37 | ) 38 | 39 | #Output all parameters bound or unbound and their associated values. 40 | 41 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 42 | 43 | #Declare function variables. 44 | 45 | [array]$functionPermissions=@() 46 | $functionRecipients=@() 47 | 48 | Out-LogFile -string "********************************************************************************" 49 | Out-LogFile -string "BEGIN Get-onPremFullMailboxAccess" 50 | Out-LogFile -string "********************************************************************************" 51 | 52 | if ($collectedData -eq $NULL) 53 | { 54 | #Start function processing. 55 | 56 | try { 57 | out-logfile -string "Gathering all on premises mailboxes." 58 | 59 | $functionRecipients = invoke-command {get-mailbox -resultsize unlimited} 60 | } 61 | catch { 62 | out-logfile -string "Error attempting to invoke command to gather all recipients." 63 | out-logfile -string $_ -isError:$TRUE 64 | } 65 | 66 | #We now have all the mailbox recipients. 67 | 68 | try { 69 | out-logfile -string "Test for mailbox permissions." 70 | 71 | $ProgressDelta = 100/($functionRecipients.count); $PercentComplete = 0; $MbxNumber = 0 72 | 73 | foreach ($recipient in $functionRecipients) 74 | { 75 | $MbxNumber++ 76 | 77 | write-progress -activity "Processing Recipient" -status $recipient.primarySMTPAddress -PercentComplete $PercentComplete 78 | 79 | $PercentComplete += $ProgressDelta 80 | 81 | if ($functionCounter -gt 1000) 82 | { 83 | #Implement function counter for long running operations - pause for 5 seconds every 1000 queries. 84 | 85 | start-sleepProgress -sleepString "Throttling for 5 seconds at 1000 operations." -sleepSeconds 5 86 | 87 | $functionCounter=0 88 | } 89 | else 90 | { 91 | $functionCounter++ 92 | } 93 | 94 | $functionPermissions+= invoke-command {Get-MailboxPermission -identity $args[0] -user $args[1]}-ArgumentList $recipient.identity,$originalDLConfiguration.samAccountName 95 | } 96 | } 97 | catch { 98 | out-logfile -string "Error attempting to invoke command to gather all mailbox permissions." 99 | out-logfile -string $_ -isError:$TRUE 100 | } 101 | 102 | write-progress -activity "Processing Recipient" -completed 103 | } 104 | elseif ($collectedData -ne $NULL) 105 | { 106 | out-logfile -string "Testing for full mailbox access rights.." 107 | 108 | $functionPermissions = $collectedData | where {($_.user.tolower()).contains($originalDLConfiguration.samAccountName.toLower())} 109 | } 110 | 111 | Out-LogFile -string "********************************************************************************" 112 | Out-LogFile -string "END Get-onPremFullMailboxAccess" 113 | Out-LogFile -string "********************************************************************************" 114 | 115 | 116 | if ($functionPermissions.count -gt 0) 117 | { 118 | out-logfile -string $functionPermissions 119 | return $functionPermissions 120 | } 121 | } -------------------------------------------------------------------------------- /Import-PowershellSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function imports the Exchange On-Premises powershell session. 5 | 6 | .DESCRIPTION 7 | 8 | This function imports the Exchange On Premises powershell session allowing exchange commands to be utilized. 9 | 10 | .PARAMETER exchangePowershellSession 11 | 12 | This is the powershell session created by new-ExchangeOnPremisesPowershell 13 | 14 | .OUTPUTS 15 | 16 | The powershell session to Exchange On-Premises. 17 | 18 | .EXAMPLE 19 | 20 | import-ExchangeOnPremisesPowershell -exchangePowershellSession session 21 | 22 | #> 23 | Function Import-PowershellSession 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | $PowershellSession, 31 | [Parameter(Mandatory = $false)] 32 | [boolean]$isAudit=$false 33 | ) 34 | 35 | #Output all parameters bound or unbound and their associated values. 36 | 37 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 38 | 39 | #Define variables that will be utilzed in the function." 40 | 41 | #Begin estabilshing the powershell session. 42 | 43 | Out-LogFile -string "********************************************************************************" 44 | Out-LogFile -string "BEGIN IMPORT-POWERSHELLSESSION" 45 | Out-LogFile -string "********************************************************************************" 46 | 47 | try 48 | { 49 | Out-LogFile -string "Importing powershell session." 50 | 51 | Import-PSSession -Session $PowershellSession -allowClobber -ErrorAction Stop 52 | } 53 | catch 54 | { 55 | Out-LogFile -string $_ -iserror:$TRUE -isAudit $isAudit 56 | } 57 | 58 | Out-LogFile -string "The powershell session imported successfully." 59 | Out-LogFile -string "END IMPORT-POWERSHELLSESSION" 60 | Out-LogFile -string "********************************************************************************" 61 | } -------------------------------------------------------------------------------- /Invoke-ADConnect.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function invokes AD Connect to sync the user if credentials were provided. 5 | 6 | .DESCRIPTION 7 | 8 | This function invokes AD Connect to sync the user if credentials were provided. 9 | 10 | .PARAMETER PowershellSessionName 11 | 12 | This is the name of the powershell session that will be used to trigger ad connect. 13 | 14 | .OUTPUTS 15 | 16 | Powershell session to use for aad connect commands. 17 | 18 | .EXAMPLE 19 | 20 | invoke-adConnect -powerShellSessionName NAME 21 | 22 | #> 23 | Function Invoke-ADConnect 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | $PowershellSessionName, 31 | [Parameter(Mandatory = $false)] 32 | $isSingleAttempt = $false 33 | ) 34 | 35 | #Output all parameters bound or unbound and their associated values. 36 | 37 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 38 | 39 | #Declare function variables. 40 | 41 | $workingPowershellSession=$null 42 | $invokeTest=$null 43 | $invokeSleep=$false 44 | 45 | #Start function processing. 46 | 47 | Out-LogFile -string "********************************************************************************" 48 | Out-LogFile -string "BEGIN INVOKE-ADCONNECT" 49 | Out-LogFile -string "********************************************************************************" 50 | 51 | #Obtain the powershell session to work with. 52 | 53 | try 54 | { 55 | $workingPowershellSession = Get-PSSession -Name $PowershellSessionName 56 | out-logfile -string $workingPowershellSession 57 | } 58 | catch 59 | { 60 | Out-LogFile -string $_ -isError:$TRUE 61 | } 62 | 63 | 64 | #Using the powershell session import the ad connect module. 65 | 66 | try 67 | { 68 | out-logfile -string "Importing ADSync module..." 69 | invoke-command -session $workingPowershellSession -ScriptBlock {Import-Module -Name 'AdSync'} *>&1 70 | } 71 | catch 72 | { 73 | Out-LogFile -string 74 | } 75 | 76 | #Establisha a retry counter. 77 | #The script will try to trigger ad connect 10 times - if not successful move on. 78 | #Eventually AD conn\ect will run on it's own or potentially there is an issue with the remote powershell session the server itself. 79 | 80 | if ($isSingleAttempt -eq $FALSE) 81 | { 82 | $doCounter=0 83 | 84 | do 85 | { 86 | if ($invokeSleep -eq $TRUE) 87 | { 88 | start-sleepProgress -sleepString "Retrying after waiting 30 seconds." -sleepSeconds 30 89 | } 90 | else 91 | { 92 | out-logfile -string "This is first attempt - skipping sleep." 93 | 94 | $invokeSleep = $true 95 | } 96 | 97 | $invokeTest = Invoke-Command -Session $workingPowershellSession -ScriptBlock {start-adsyncsynccycle -policyType 'Delta'} *>&1 98 | 99 | out-logfile -string $invokeTest 100 | 101 | if ($invokeTest.result -ne "Success") 102 | { 103 | out-logFile -string "An error has occurred - this is not necessarily uncommon." 104 | out-logFile -string $invokeTest.exception.toString() 105 | } 106 | else 107 | { 108 | out-logfile -string "The results of the AD Sync." 109 | out-logfile -string $invokeTest.result 110 | } 111 | 112 | $doCounter=$doCounter+1 113 | 114 | out-logfile ("Retry counter incremented: "+$doCounter.tostring()) 115 | 116 | } until (($invokeTest.result -eq "Success") -or ($doCounter -eq 10)) 117 | } 118 | else 119 | { 120 | out-logfile -string "Attempting one time invocation of AD Connect for multi-threaded retry." 121 | 122 | $invokeTest = Invoke-Command -Session $workingPowershellSession -ScriptBlock {start-adsyncsynccycle -policyType 'Delta' -errorAction Continue} *>&1 123 | 124 | out-logfile -string $invokeTest 125 | 126 | if ($invokeTest.result -ne "Success") 127 | { 128 | out-logFile -string "An error has occurred - this is not necessarily uncommon." 129 | out-logFile -string $invokeTest.exception.toString() 130 | } 131 | else { 132 | out-logfile -string "The results of the AD Sync." 133 | out-logfile -string $invokeTest.result 134 | } 135 | } 136 | 137 | if ($doCounter -eq 10) 138 | { 139 | out-logfile -string "AD Connect was not triggered due to retry limit reached." 140 | out-logfile -string "Consider reviewing the AD Connect server for any potential issues." 141 | } 142 | 143 | Out-LogFile -string "Function to trigger AD Connect." 144 | 145 | Out-LogFile -string "END INVOKE-ADCONNECT" 146 | Out-LogFile -string "********************************************************************************" 147 | } -------------------------------------------------------------------------------- /Invoke-ADReplication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function triggers ad replication inbound or outbound from the DC where we process changes. 5 | 6 | .DESCRIPTION 7 | 8 | This function triggers ad replication inbound or outbound from the DC where we process changes. 9 | 10 | .PARAMETER PowershellSessionName 11 | 12 | This is the name of the powershell session that will be used to trigger ad connect. 13 | 14 | .PARAMETER GlobalCatalogServer 15 | 16 | This is the global catalog server where replication will be triggered. 17 | 18 | .OUTPUTS 19 | 20 | Powershell session to use for aad connect commands. 21 | 22 | .EXAMPLE 23 | 24 | invoke-adreplication -powershellsessionName NAME -globalCatalogServer NAME 25 | 26 | #> 27 | Function Invoke-ADReplication 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | $PowershellSessionName, 35 | [Parameter(Mandatory = $true)] 36 | $globalCatalogServer 37 | ) 38 | 39 | #Output all parameters bound or unbound and their associated values. 40 | 41 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 42 | 43 | #Declare function variables. 44 | 45 | $workingPowershellSession=$NULL 46 | $invokeTest=$null 47 | 48 | #Start function processing. 49 | 50 | Out-LogFile -string "********************************************************************************" 51 | Out-LogFile -string "BEGIN INVOKE-ADREPLICATION" 52 | Out-LogFile -string "********************************************************************************" 53 | 54 | #Obtain the powershell session to work with. 55 | 56 | try 57 | { 58 | $workingPowershellSession = Get-PSSession -Name $PowershellSessionName 59 | } 60 | catch 61 | { 62 | Out-LogFile -string $_ -isError:$TRUE 63 | } 64 | 65 | 66 | #Using the powershell session import the ad connect module. 67 | 68 | try 69 | { 70 | out-logfile -string "Replication domain controllers inbound." 71 | 72 | $invokeTest=invoke-command -session $workingPowershellSession -ScriptBlock { repadmin /syncall /A } *>&1 73 | 74 | $invokeTest = $invokeTest -join "`r`n" 75 | 76 | out-logfile -string $invokeTest 77 | } 78 | catch 79 | { 80 | Out-LogFile -string $_ -isError:$TRUE 81 | } 82 | 83 | try 84 | { 85 | out-logfile -string "Replication domain controllers outbound." 86 | 87 | $invokeTest=invoke-command -session $workingPowershellSession -ScriptBlock { repadmin /syncall /APe } *>&1 88 | 89 | $invokeTest = $invokeTest -join "`r`n" 90 | 91 | out-logfile -string $invokeTest 92 | } 93 | catch 94 | { 95 | Out-LogFile -string $_ -isError:$TRUE 96 | } 97 | 98 | Out-LogFile -string "END INVOKE-ADReplication" 99 | Out-LogFile -string "********************************************************************************" 100 | } -------------------------------------------------------------------------------- /New-LogFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function tests for and creates the log file / log file path for the script. 5 | 6 | .DESCRIPTION 7 | 8 | This function tests for and creates the log file / log file path for the script. 9 | 10 | .PARAMETER logFolderPath 11 | 12 | The path of the log file. 13 | 14 | .PARAMETER groupSMTPAddress 15 | 16 | The SMTP address of the group being migrated - this will be parsed for the log file name. 17 | 18 | .OUTPUTS 19 | 20 | Ensure the directory exists. 21 | Establishes the logfile path/name for subsequent function calls. 22 | 23 | .EXAMPLE 24 | 25 | new-logfile -groupSMTPAddress ADDRESS -logFolderPath LOGFOLDERPATH 26 | 27 | #> 28 | Function new-LogFile 29 | { 30 | [cmdletbinding()] 31 | 32 | Param 33 | ( 34 | [Parameter(Mandatory = $true)] 35 | [string]$groupSMTPAddress, 36 | [Parameter(Mandatory = $true)] 37 | [string]$logFolderPath 38 | ) 39 | 40 | #Define the string separator and then separate the string. 41 | 42 | $groupsSMTPAddress = $groupSMTPAddress.trim() 43 | [string]$separator="@" 44 | [array]$fileNameSplit = $groupSMTPAddress.Split($separator) 45 | 46 | #First entry in split array is the prefix of the group - use that for log file name. 47 | #The SMTP address may contain letters that are not permitted in a file name - for example ?. 48 | #Using regex and a pattern to replace invalid file name characters with a - 49 | 50 | [string]$fileName=$fileNameSplit[0]+".log" 51 | $pattern = $pattern = '[' + ([System.IO.Path]::GetInvalidFileNameChars() -join '').Replace('\','\\') + ']+' 52 | $fileName=[regex]::Replace($fileName, $pattern,"-") 53 | 54 | # Get our log file path 55 | 56 | $logFolderPath = $logFolderPath+$global:staticFolderName 57 | 58 | #Since $logFile is defined in the calling function - this sets the log file name for the entire script 59 | 60 | $global:LogFile = Join-path $logFolderPath $fileName 61 | 62 | #Test the path to see if this exists if not create. 63 | 64 | [boolean]$pathExists = Test-Path -Path $logFolderPath 65 | 66 | if ($pathExists -eq $false) 67 | { 68 | try 69 | { 70 | #Path did not exist - Creating 71 | 72 | New-Item -Path $logFolderPath -Type Directory 73 | } 74 | catch 75 | { 76 | throw $_ 77 | } 78 | } 79 | 80 | out-logfile -string "================================================================================" 81 | out-logfile -string "START LOG FILE" 82 | out-logfile -string "================================================================================" 83 | } -------------------------------------------------------------------------------- /New-PowershellSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function creates a powershell session to an on premises server to invoke winRM commands. 5 | 6 | .DESCRIPTION 7 | 8 | This function creates a powershell session to an on premises server to invoke winRM commands. 9 | 10 | .PARAMETER Credential 11 | 12 | This is the credential that will be utilized to establish the connection 13 | 14 | .PARAMETER Server 15 | 16 | This is the server that the connection will be made to. 17 | 18 | .PARAMETER PowershellSessionName 19 | 20 | This is the name of the powershell session that will be created. 21 | 22 | .PARAMETER connectionURI 23 | 24 | The web address for remote powershell sessions. 25 | 26 | .PARAMETER authenticationType 27 | 28 | Specifies to user kerberos or basic authentication is auth type is required. 29 | 30 | .PARAMETER configurationName 31 | 32 | The configuration name for the remote winRM sessions. 33 | 34 | .PARAMETER allowRedirection 35 | 36 | Determines if redirection is allowed on the winRM connection. 37 | 38 | .PARAMETER requiresImport 39 | 40 | Returns the PS session to the caller if import is required. 41 | 42 | .OUTPUTS 43 | 44 | Powershell session to use for aad connect commands. 45 | 46 | .EXAMPLE 47 | 48 | new-PowershellSession -Server SERVER -Credential Credential -PowershellSessionName Name 49 | 50 | #> 51 | Function New-PowershellSession 52 | { 53 | [cmdletbinding()] 54 | 55 | Param 56 | ( 57 | [Parameter(ParameterSetName="NotOnline",Mandatory = $true)] 58 | [Parameter(ParameterSetName="Online",Mandatory = $true)] 59 | [pscredential]$Credentials, 60 | [Parameter(ParameterSetName="NotOnline",Mandatory = $true)] 61 | [string]$Server, 62 | [Parameter(ParameterSetName="NotOnline",Mandatory = $true)] 63 | [Parameter(ParameterSetName="Online",Mandatory = $true)] 64 | [string]$PowershellSessionName, 65 | [Parameter(ParameterSetName="Online",Mandatory = $true)] 66 | [string]$connectionURI, 67 | [Parameter(ParameterSetName="Online",Mandatory = $true)] 68 | [Parameter(ParameterSetName="NotOnline",Mandatory = $true)] 69 | [string]$authenticationType, 70 | [Parameter(ParameterSetName="Online",Mandatory = $true)] 71 | [string]$configurationName, 72 | [Parameter(ParameterSetName="Online")] 73 | [boolean]$allowRedirection=$FALSE, 74 | [Parameter(ParameterSetName="Online")] 75 | [boolean]$requiresImport=$FALSE, 76 | [Parameter(ParameterSetName="NotOnline",Mandatory = $false)] 77 | [Parameter(ParameterSetName="Online",Mandatory = $false)] 78 | [boolean]$isAudit=$FALSE 79 | ) 80 | 81 | #Output all parameters bound or unbound and their associated values. 82 | 83 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 84 | 85 | #Declare function variables. 86 | 87 | $sessionToImport=$NULL 88 | 89 | #Start function processing. 90 | 91 | Out-LogFile -string "********************************************************************************" 92 | Out-LogFile -string "BEGIN NEW-POWERSHELLSESSION" 93 | Out-LogFile -string "********************************************************************************" 94 | 95 | try 96 | { 97 | if ($requiresImport -eq $FALSE) 98 | { 99 | #The session was flagged by the caller as requiring import. 100 | #This would usually be reserved for things like Exchange On Premises / Exchange Online 101 | 102 | Out-LogFile -string "Creating the powershell to server." 103 | New-PSSession -computername $Server -credential $Credentials -name $PowershellSessionName -authentication $authenticationType -errorAction STOP 104 | } 105 | elseif ($requiresImport -eq $TRUE) 106 | { 107 | #No import is required - this is a local powershell session 108 | 109 | Out-LogFile -string "Creating the powershell to server that requires import." 110 | $sessiontoimport=New-PSSession -ConfigurationName $configurationName -ConnectionUri $connectionURI -Credential $credentials -AllowRedirection:$allowRedirection -Authentication $authenticationType -name $PowershellSessionName 111 | } 112 | } 113 | catch 114 | { 115 | Out-LogFile -string $_ -isError:$TRUE -isAudit $isAudit 116 | } 117 | 118 | Out-LogFile -string "The powershell session was created successfully." 119 | 120 | Out-LogFile -string "END NEW-POWERSHELLSESSION" 121 | Out-LogFile -string "********************************************************************************" 122 | 123 | #This function is designed to open local and remote powershell sessions. 124 | #If the session requires import - for example exchange - return the session for later work. 125 | #If not no return is required. 126 | 127 | if ($requiresImport -eq $TRUE) 128 | { 129 | return $sessionToImport 130 | } 131 | } -------------------------------------------------------------------------------- /New-StatusFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function tests for and creates the log file / log file path for the script. 5 | 6 | .DESCRIPTION 7 | 8 | This function tests for and creates the log file / log file path for the script. 9 | 10 | .PARAMETER logFolderPath 11 | 12 | The path of the log file. 13 | 14 | .OUTPUTS 15 | 16 | Ensure the directory exists. 17 | Establishes the logfile path/name for subsequent function calls. 18 | 19 | .EXAMPLE 20 | 21 | new-statusFile -logFolderPath LOGFOLDERPATH 22 | 23 | #> 24 | Function new-statusFile 25 | { 26 | [cmdletbinding()] 27 | 28 | Param 29 | ( 30 | [Parameter(Mandatory = $true)] 31 | [string]$logFolderPath 32 | ) 33 | 34 | #Output all parameters bound or unbound and their associated values. 35 | 36 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 37 | 38 | # Get our log file path 39 | 40 | $logFolderPath = $logFolderPath+$global:statusPath 41 | 42 | #Set the global status path. 43 | 44 | $global:fullStatusPath = $logFolderPath 45 | 46 | #Test the path to see if this exists if not create. 47 | 48 | [boolean]$pathExists = Test-Path -Path $logFolderPath 49 | 50 | if ($pathExists -eq $false) 51 | { 52 | try 53 | { 54 | #Path did not exist - Creating 55 | 56 | New-Item -Path $logFolderPath -Type Directory 57 | } 58 | catch 59 | { 60 | throw $_ 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Out-LogFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function provides the logging functionality of the script. 5 | 6 | .DESCRIPTION 7 | 8 | Logging 9 | 10 | .PARAMETER string 11 | 12 | The string to be written to the log file. 13 | 14 | .PARAMETER isError 15 | 16 | Boolean value to signify exception / log it / terminate script. 17 | 18 | .OUTPUTS 19 | 20 | Logs all activities and backs up all original data to the log folder directory. 21 | 22 | .EXAMPLE 23 | 24 | Out-LogFile -string "MESSAGE" -isError BOOLEAN 25 | 26 | #> 27 | Function Out-LogFile 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | $String, 35 | [Parameter(Mandatory = $false)] 36 | [boolean]$isError=$FALSE, 37 | [Parameter(Mandatory = $false)] 38 | [boolean]$isAudit=$FALSE 39 | ) 40 | 41 | # Get the current date 42 | 43 | [string]$date = Get-Date -Format G 44 | 45 | # Build output string 46 | #In this case since I abuse the function to write data to screen and record it in log file 47 | #If the input is not a string type do not time it just throw it to the log. 48 | 49 | if ($string.gettype().name -eq "String") 50 | { 51 | [string]$logstring = ( "[" + $date + "] - " + $string) 52 | } 53 | else 54 | { 55 | $logString = $String 56 | } 57 | 58 | # Write everything to our log file and the screen 59 | 60 | $logstring | Out-File -FilePath $global:LogFile -Append 61 | 62 | #Write to the screen the information passed to the log. 63 | 64 | if ($string.gettype().name -eq "String") 65 | { 66 | Write-Host $logString 67 | } 68 | else 69 | { 70 | write-host $logString | select-object -expandProperty * 71 | } 72 | 73 | #If the output to the log is terminating exception - throw the same string. 74 | 75 | if ($isError -eq $TRUE) 76 | { 77 | #Ok - so here's the deal. 78 | #By default error action is continue. IN all my function calls I use STOP for the most part. 79 | #In this case if we hit this error code - one of two things happen. 80 | #If the call is from another function that is not in a do while - the error is logged and we continue with exiting. 81 | #If the call is from a function in a do while - write-error rethrows the exception. The exception is caught by the caller where a retry occurs. 82 | #This is how we end up logging an error then looping back around. 83 | 84 | write-error $logString 85 | 86 | #Now if we're not in a do while we end up here -> go ahead and create the status file this was not a retryable operation and is a hard failure. 87 | 88 | if ($global:ThreadNumber -gt 0) 89 | { 90 | out-statusFile -threadNumber $global:ThreadNumber 91 | } 92 | 93 | disable-allPowerShellSessions 94 | 95 | if ($isAudit -eq $FALSE) 96 | { 97 | Start-ArchiveFiles -isSuccess:$FALSE -logFolderPath $logFolderPath 98 | } 99 | exit 100 | } 101 | } -------------------------------------------------------------------------------- /Out-XMLFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function backs up the data for relevant entries to XML. 5 | 6 | .DESCRIPTION 7 | 8 | Backup to XML 9 | 10 | .PARAMETER itemToExport 11 | 12 | This is the item to export to XML 13 | 14 | .PARAMETER logFolderPath 15 | 16 | The path of the log file. 17 | 18 | .PARAMETER itemNameToExport 19 | 20 | What the XML file will be named. 21 | 22 | .OUTPUTS 23 | 24 | Backs up the associated information to XML. 25 | 26 | .EXAMPLE 27 | 28 | Out-XMLFile -itemToExport ITEM -logFolderPath Path -itemNameToExport 29 | 30 | #> 31 | Function Out-XMLFile 32 | { 33 | [cmdletbinding()] 34 | 35 | Param 36 | ( 37 | [Parameter(Mandatory = $true)] 38 | $itemToExport, 39 | [Parameter(Mandatory = $true)] 40 | [string]$itemNameToExport 41 | ) 42 | 43 | Out-LogFile -string "********************************************************************************" 44 | Out-LogFile -string "BEGIN OUT-XMLFILE" 45 | Out-LogFile -string "********************************************************************************" 46 | 47 | #Declare function variables. 48 | 49 | $fileName = $itemNameToExport+".xml" 50 | 51 | #Update the log folder path to include the static folder. 52 | 53 | $logFolderPath = $logFolderPath+$global:staticFolderName 54 | 55 | # Get our log file path and combine it with the filename 56 | 57 | $LogFile = Join-path $logFolderPath $fileName 58 | 59 | #Write our variables to the log. 60 | 61 | out-logfile -string ("XML File Name = "+$fileName) 62 | out-logfile -string ("Log Folder Path = "+$logFolderPath) 63 | out-logfile -string ("Log File = "+$LogFile) 64 | 65 | # Write everything to our log file and the screen 66 | 67 | try 68 | { 69 | $itemToExport | export-CLIXML -path $LogFile 70 | } 71 | catch 72 | { 73 | throw $_ 74 | } 75 | 76 | Out-LogFile -string "END OUT-XMLFILE" 77 | Out-LogFile -string "********************************************************************************" 78 | } -------------------------------------------------------------------------------- /Remove-StringSpace.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function removes all spaces from any user inputted string. Prevents trianing and leading spaces. 5 | 6 | .DESCRIPTION 7 | 8 | This function removes all spaces from any user inputted string. Prevents trianing and leading spaces. 9 | 10 | .PARAMETER stringToFix 11 | 12 | The string to remove all spaces from. 13 | 14 | .OUTPUTS 15 | 16 | Empty status file directory. 17 | 18 | .EXAMPLE 19 | 20 | remove-StringSpace -stringToFix STRING 21 | 22 | #> 23 | Function remove-StringSpace 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $false)] 30 | [string]$stringToFix=0 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | 38 | Out-LogFile -string "********************************************************************************" 39 | Out-LogFile -string "BEGIN remove-StringSpace" 40 | Out-LogFile -string "********************************************************************************" 41 | 42 | out-logfile -string ("String to remove spaces: "+$stringToFix) 43 | out-logfile -string ("String Length "+$stringToFix.length.toString()) 44 | 45 | $workingString = $stringToFix.trim() 46 | 47 | out-logfile -string ("String with spaces removed: "+$workingString) 48 | out-logfile -string ("String Length "+$workingString.length.toString()) 49 | 50 | return $workingString 51 | 52 | Out-LogFile -string "********************************************************************************" 53 | Out-LogFile -string "END remove-StringSpace" 54 | Out-LogFile -string "********************************************************************************" 55 | } -------------------------------------------------------------------------------- /Remove-statusFiles.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function removes all status files in the status file directory. 5 | 6 | .DESCRIPTION 7 | 8 | This function removes all status files in the status file directory. 9 | 10 | .PARAMETER functionThreadNumber 11 | 12 | The thread number of the status file to remove. 13 | 14 | .PARAMETER fullCleanup 15 | 16 | Determines if all status files should be removed. 17 | 18 | .OUTPUTS 19 | 20 | Empty status file directory. 21 | 22 | .EXAMPLE 23 | 24 | remove-statusFiles -functionThreadNumber 1 25 | 26 | #> 27 | Function remove-statusFiles 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $false)] 34 | [int]$functionThreadNumber=0, 35 | [Parameter(Mandatory = $false)] 36 | [boolean]$fullCleanup=$FALSE 37 | ) 38 | 39 | #Output all parameters bound or unbound and their associated values. 40 | 41 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 42 | 43 | [array]$threadStatus="ThreadZeroStatus.txt","ThreadOneStatus.txt","ThreadTwoStatus.txt","ThreadThreeStatus.txt","ThreadFourStatus.txt","ThreadFiveStatus.txt","ThreadSixStatus.txt","ThreadSevenStatus.txt","ThreadEightStatus.txt","ThreadNineStatus.txt","ThreadTenStatus.txt" 44 | 45 | [string]$functionPath=$NULL 46 | 47 | 48 | if ($fullCleanUp -eq $FALSE) 49 | { 50 | Out-LogFile -string "********************************************************************************" 51 | Out-LogFile -string "BEGIN remove-StatusFile" 52 | Out-LogFile -string "********************************************************************************" 53 | 54 | $functionPath=Join-path $global:fullStatusPath $threadStatus[$functionThreadNumber] 55 | 56 | out-logfile -string $functionPath 57 | } 58 | else 59 | { 60 | $functionPath=$global:fullStatusPath+"*" 61 | } 62 | 63 | try 64 | { 65 | if ($fullCleanup -eq $FALSE) 66 | { 67 | out-logfile -string "Removing files from the status directory." 68 | } 69 | 70 | remove-item -path $functionPath -force -errorAction STOP 71 | } 72 | catch 73 | { 74 | if ($fullCleanup -eq $FALSE) 75 | { 76 | out-logfile -string "Error removing log files." -isError:$TRUE 77 | } 78 | else 79 | { 80 | $_ 81 | } 82 | 83 | } 84 | 85 | if ($fullCleanup -eq $FALSE) 86 | { 87 | Out-LogFile -string "********************************************************************************" 88 | Out-LogFile -string "END remove-StatusFile" 89 | Out-LogFile -string "********************************************************************************" 90 | } 91 | } -------------------------------------------------------------------------------- /SampleMigrationScript.txt: -------------------------------------------------------------------------------- 1 | #Define on premises credentials. (This assumes credential XMLs were previously defined.) 2 | 3 | $adCred = import-cliXML c:\path\adCred.XML 4 | $entraIDCred = import-cliXML c:\path\entraIDCred.XML 5 | $exchangeOnPremCred = import-cliXML c:\path\exchangeOnPremCred.XML 6 | 7 | #Define Active Directory information 8 | 9 | $globalCatalogServer = "gc.contoso.local" 10 | 11 | #Define EntraID Connect information 12 | 13 | $aadConnectServer = "aadConnect.contoso.local" 14 | 15 | $Define Exchange On Premises information 16 | 17 | $exchangeServer = "exchange.contoso.local" 18 | 19 | #Define ms graph connection information 20 | 21 | $msGraphTenantID="68cef4f8-9392-496c-b414-9bbb61246d3b" 22 | $msGraphCertificateThumbprint="FC92991B21219F178AFB37C12DF231B6AFC3D790" 23 | $msGraphApplicationID="49390024-2b8c-46f3-a332-f1ae849267b4" 24 | 25 | #Define Exchange Online connection information 26 | 27 | $exchangeOnlineCertificateThumbPrint="FC92991B21219F178AFB37C12DF231B6AFC3D790" 28 | $exchangeOnlineOrganizationName="contoso.onmicrosoft.com" 29 | $exchangeOnlineAppID="49390024-2b8c-46f3-a332-f1ae849267b4" 30 | 31 | #Define additional parameters 32 | 33 | $enableHybridMailFlow = $TRUE 34 | $logFolderPath = "c:\DLMigrations\Logs" 35 | $dnNoSyncOU = "OU=NoSync,DC=contoso,DC=local" 36 | $groupSMTPAddress = "group@contoso.com" 37 | 38 | #Execute common migration command 39 | 40 | start-distributionListMigration -groupSMTPAddress $groupSMTPAddress -globalCatalogServer $globalCatalogServer -activeDirectoryCredential $adCred -aadConnectServer $aadConnectServer -aadConnectCredential $entrIDCred -exchangeServer $exchangeServer -exchangeCredential $exchangeOnPremCred -msGraphTenantID $msGraphTenantID -msGraphCertificateThumbprint $msGraphCertificateThumbprint -msGraphApplicationID $msGraphApplicationID -exchangeOnlineAppID $exchangeOnlineAppID -exchangeOnlineCertificateThumbPrint $exchangeOnlineCertificateThumbPrint -exchangeOnlineOrganizationName $exchangeOnlineOrganizationName -enableHybridMailFlow $enableHybridMailFlow -logFolderPath $logFolderPath -dnNoSyncOU $dnNoSyncOU -------------------------------------------------------------------------------- /Set-NewDLName.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function add a character to the DL name if exchange hybrid is enabled (allows for the dynamic group creation.) 5 | 6 | .DESCRIPTION 7 | 8 | This function add a character to the DL name if exchange hybrid is enabled (allows for the dynamic group creation.) 9 | 10 | .PARAMETER GlobalCatalogServer 11 | 12 | The global catalog to make the query against. 13 | 14 | .PARAMETER DN 15 | 16 | The original DN of the object. 17 | 18 | .PARAMETER DLName 19 | 20 | The name of the DL from the original configuration. 21 | 22 | .PARAMETER DLSamAccountName 23 | 24 | The original DN of the object. 25 | 26 | .PARAMETER adCredential 27 | 28 | .OUTPUTS 29 | 30 | None 31 | 32 | .EXAMPLE 33 | 34 | set-newDLName -dlConfiguration dlConfiguration -globalCatalogServer globalCatalogServer 35 | 36 | #> 37 | Function set-newDLName 38 | { 39 | [cmdletbinding()] 40 | 41 | Param 42 | ( 43 | [Parameter(Mandatory = $true)] 44 | [string]$globalCatalogServer, 45 | [Parameter(Mandatory = $true)] 46 | $dlName, 47 | [Parameter(Mandatory = $true)] 48 | $dlSAMAccountName, 49 | [Parameter(Mandatory = $true)] 50 | $DN, 51 | [Parameter(Mandatory = $true)] 52 | $adCredential, 53 | [Parameter(Mandatory = $false)] 54 | [ValidateSet("Basic","Negotiate")] 55 | $activeDirectoryAuthenticationMethod="Negotiate" 56 | ) 57 | 58 | #Output all parameters bound or unbound and their associated values. 59 | 60 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 61 | 62 | #Declare function variables. 63 | 64 | [string]$functionGroupName=$NULL #Holds the calculated name. 65 | [string]$functionGroupSAMAccountName=$NULL #Holds the calculated sam account name. 66 | [string]$functionMaxLength = 64 67 | [string]$functionGroupNameCharacter = "!" 68 | 69 | #Start function processing. 70 | 71 | Out-LogFile -string "********************************************************************************" 72 | Out-LogFile -string "BEGIN SET-NEWDLNAME" 73 | Out-LogFile -string "********************************************************************************" 74 | 75 | #Establish new names 76 | 77 | if ($dlName.length -eq $functionMaxLength) 78 | { 79 | out-logfile -string "Group name is 64 characters - truncate single character to support rename." 80 | 81 | [string]$functionGroupName = $dlName.substring(0,$functionMaxLength-1)+$functionGroupNameCharacter 82 | } 83 | else 84 | { 85 | [string]$functionGroupName = $dlname+$functionGroupNameCharacter 86 | out-logfile -string "Group name does not exceed 64 characters - rename as normal." 87 | } 88 | 89 | [string]$functionGroupSAMAccountName = $dlSAMAccountName+"!" 90 | 91 | out-logfile -string ("New group name = "+$functionGroupName) 92 | out-logfile -string ("New group sam account name = "+$functionGroupSAMAccountName) 93 | 94 | #Get the specific user using ad providers. 95 | 96 | try 97 | { 98 | Out-LogFile -string "Set the AD group name." 99 | 100 | set-adGroup -identity $dn -samAccountName $functionGroupSAMAccountName -server $globalCatalogServer -Credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 101 | } 102 | catch 103 | { 104 | Out-LogFile -string $_ -isError:$TRUE 105 | } 106 | 107 | try 108 | { 109 | out-logfile -string "Setting the new group name.." 110 | 111 | rename-adobject -identity $dn -newName $functionGroupName -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 112 | } 113 | catch 114 | { 115 | Out-LogFile -string $_ -isError:$true 116 | } 117 | 118 | Out-LogFile -string "END Set-NewDLName" 119 | Out-LogFile -string "********************************************************************************" 120 | } -------------------------------------------------------------------------------- /Start-ArchiveFiles.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function archives the files associated with the distribution list migration. 5 | 6 | .DESCRIPTION 7 | 8 | his function archives the files associated with the distribution list migration. 9 | 10 | .PARAMETER isSuccess 11 | 12 | .OUTPUTS 13 | 14 | No return. 15 | 16 | .EXAMPLE 17 | 18 | start-archiveFiles -isSuccess:$TRUE 19 | 20 | #> 21 | Function Start-ArchiveFiles 22 | { 23 | [cmdletbinding()] 24 | 25 | Param 26 | ( 27 | [Parameter(Mandatory = $true)] 28 | [boolean]$isSuccess=$FALSE, 29 | [Parameter(Mandatory = $true)] 30 | [string]$logFolderPath=$NULL, 31 | [Parameter(Mandatory = $false)] 32 | [boolean]$isHealthCheck=$FALSE 33 | ) 34 | 35 | #Output all parameters bound or unbound and their associated values. 36 | 37 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 38 | 39 | out-logFile -string "Archiving files associated with run." 40 | 41 | if ($isHealthCheck -eq $TRUE) 42 | { 43 | $functionDate = Get-Date -Format FileDateTime 44 | $functionDate = "PreReqTest-"+$functionDate 45 | } 46 | else 47 | { 48 | $functionDate = Get-Date -Format FileDateTime 49 | } 50 | 51 | $functionNameSplit = $global:logFile.split("\") 52 | 53 | out-logfile -string "Split string for group name." 54 | out-logfile -string $functionNameSplit 55 | 56 | $functionNameSplit = $functionNameSplit[-1].split(".") 57 | 58 | if ($functionNameSplit.count -gt 2) 59 | { 60 | out-logfile -string "DL Name contains one or more periods." 61 | 62 | [string]$tempName = "" 63 | 64 | for ($i = 0 ; $i -lt $functionNameSplit.count - 1 ; $i++) 65 | { 66 | $tempName = $tempName + $functionNameSplit[$i] 67 | 68 | if (($i+1) -lt ($functionNameSplit.count - 1)) 69 | { 70 | $tempName = $tempName + "." 71 | } 72 | } 73 | 74 | out-logfile -string $tempName 75 | 76 | $functionNameSplit = $tempName 77 | } 78 | else 79 | { 80 | $functionNameSplit = $functionNameSplit[0] 81 | } 82 | 83 | out-logfile -string "Split string for group name." 84 | out-logfile -string $functionNameSplit 85 | 86 | if ($isSuccess -eq $TRUE) 87 | { 88 | write-shamelessPlug 89 | 90 | out-logfile -string "Success - renaming directory." 91 | 92 | $functionFolderName = $functionNameSplit+"-Success" 93 | $functionFolderName = $functionDate+"-"+$functionFolderName 94 | $functionOriginalPath= $logFolderPath+$global:staticFolderName 95 | 96 | out-logfile -string $functionFolderName 97 | out-logfile -string $functionOriginalPath 98 | 99 | rename-item -path $functionOriginalPath -newName $functionFolderName 100 | } 101 | else 102 | { 103 | write-shamelessPlug 104 | 105 | out-logfile -string "FAILED - renaming directory." 106 | 107 | $functionFolderName = $functionNameSplit+"-FAILED" 108 | $functionFolderName = $functionDate+"-"+$functionFolderName 109 | $functionOriginalPath= $logFolderPath+$global:staticFolderName 110 | 111 | out-logfile -string $functionFolderName 112 | out-logfile -string $functionOriginalPath 113 | 114 | $doCounter=0 115 | $stopLoop=$FALSE 116 | 117 | do { 118 | try { 119 | rename-item -path $functionOriginalPath -newName $functionFolderName -errorAction Stop 120 | 121 | $stopLoop=$true 122 | } 123 | catch { 124 | if ($doCounter -gt 5) 125 | { 126 | $stopLoop-$TRUE 127 | } 128 | else 129 | { 130 | start-sleep -s 5 131 | $doCounter=$doCounter+1 132 | } 133 | } 134 | } until ($stopLoop -eq $TRUE) 135 | } 136 | } -------------------------------------------------------------------------------- /Test-AcceptedDomain.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function tests each accepted domain on the group to ensure it appears in Office 365. 5 | 6 | .DESCRIPTION 7 | 8 | This function tests each accepted domain on the group to ensure it appears in Office 365. 9 | 10 | .EXAMPLE 11 | 12 | Test-AcceptedDomain -originalDLConfiguration $originalDLConfiguration 13 | 14 | #> 15 | Function Test-AcceptedDomain 16 | { 17 | [cmdletbinding()] 18 | 19 | Param 20 | ( 21 | [Parameter(Mandatory = $true)] 22 | $originalDLConfiguration 23 | ) 24 | 25 | #Output all parameters bound or unbound and their associated values. 26 | 27 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 28 | 29 | #Define variables that will be utilzed in the function. 30 | 31 | [array]$originalDLAddresses=@() 32 | [array]$originalDLDomainNames=@() 33 | 34 | #Initiate the test. 35 | 36 | Out-LogFile -string "********************************************************************************" 37 | Out-LogFile -string "BEGIN Test-AcceptedDomain" 38 | Out-LogFile -string "********************************************************************************" 39 | 40 | foreach ($address in $originalDLConfiguration.proxyAddresses) 41 | { 42 | Out-logfile -string "Testing proxy address for SMTP" 43 | out-logfile -string $address 44 | 45 | if ($address -like "smtp*") 46 | { 47 | out-logfile -string ("Address is smtp address: "+$address) 48 | 49 | $tempAddress=$address.split("@") 50 | 51 | $originalDLDomainNames+=$tempAddress[1] 52 | } 53 | else 54 | { 55 | out-logfile -string ("Address is not an SMTP Address - skip.") 56 | } 57 | } 58 | 59 | #It is possible that the group does not have proxy address but just mail - this is now a supported scenario. 60 | #To get this far the object has to have mail. 61 | 62 | out-logfile -string ("The mail address is: "+$originalDLConfiguration.mail) 63 | $tempAddress=$originalDLConfiguration.mail.split("@") 64 | $originalDLDomainNames+=$tempAddress[1] 65 | 66 | 67 | $originalDLDomainNames=$originalDLDomainNames | select-object -Unique 68 | 69 | out-logfile -string "Unique domain names on the group." 70 | out-logfile -string $originalDLDomainNames 71 | 72 | foreach ($domain in $originalDLDomainNames) 73 | { 74 | out-logfile -string "Testing Office 365 for Domain Name." 75 | 76 | if (get-o365acceptedDomain -identity $domain) 77 | { 78 | out-logfile -string ("Domain exists in Office 365. "+$domain) 79 | } 80 | else 81 | { 82 | out-logfile -string $domain 83 | out-logfile -string "Group cannot be migrated until the domain is an accepted domain in Office 365 or removed from the group." 84 | out-logfile -string "Email address exists on group that is not in Office 365." -isError:$TRUE 85 | } 86 | } 87 | 88 | Out-LogFile -string "END Test-AcceptedDomain" 89 | Out-LogFile -string "********************************************************************************" 90 | } -------------------------------------------------------------------------------- /Test-OutboundConnector.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function tests all outbound connectors for centralized mail transport. 5 | 6 | .DESCRIPTION 7 | 8 | This function tests all outbound connectors for centralized mail transport. 9 | 10 | .EXAMPLE 11 | 12 | Test-OutboundConnector -overrideCentralizedMailTransportEnabled:$TRUE 13 | 14 | #> 15 | Function Test-OutboundConnector 16 | { 17 | [cmdletbinding()] 18 | 19 | Param 20 | ( 21 | [Parameter(Mandatory = $true)] 22 | $overrideCentralizedMailTransportEnabled 23 | ) 24 | 25 | #Output all parameters bound or unbound and their associated values. 26 | 27 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 28 | 29 | #Define variables that will be utilzed in the function. 30 | 31 | [array]$exchangeOnlineOutboundConnectors=@() 32 | 33 | #Initiate the test. 34 | 35 | Out-LogFile -string "********************************************************************************" 36 | Out-LogFile -string "BEGIN Test-OutboundConnectors" 37 | Out-LogFile -string "********************************************************************************" 38 | 39 | $exchangeOnlineOutboundConnectors = get-o365OutboundConnector 40 | 41 | if ($overrideCentralizedMailTransportEnabled -eq $FALSE) 42 | { 43 | foreach ($outboundConnector in $exchangeOnlineOutboundConnectors) 44 | { 45 | if ($outboundConnector.RouteAllMessagesViaOnPremises -eq $TRUE) 46 | { 47 | out-logfile -string "***WARNING***" 48 | out-logfile -string "Centralized transport is enabled." 49 | out-logfile -string "When centralized mail transport is enabled - if the migrated group contains any on premises mailboxes the public MX is utilized for routing." 50 | out-logfile -string "If not properly tested this could lead to NDRs or messages appearing as external to on premises resources." 51 | out-logfile -string "Migrating this DL can only be accomplished by acknowledging centralized mail transport is enabled using the -overrideCentralizedMailTransportEnabled:$TRUE" 52 | out-logfile -string $outboundConnector.name 53 | out-logfile -string $outboundConnector.RouteAllMessagesViaOnPremises 54 | out-logfile -string "***WARNING***" -isError:$true 55 | } 56 | else 57 | { 58 | out-logfile -string "Connector not enabled for centralized mail transport." 59 | out-logfile -string $outboundConnector.Name 60 | out-logfile -string $outboundConnector.RouteAllMessagesViaOnPremises 61 | } 62 | } 63 | } 64 | else 65 | { 66 | foreach ($outboundConnector in $exchangeOnlineOutboundConnectors) 67 | { 68 | if ($outboundConnector.RouteAllMessagesViaOnPremises -eq $TRUE) 69 | { 70 | out-logfile -string "***WARNING***" 71 | out-logfile -string "Centralized transport is enabled." 72 | out-logfile -string "When centralized mail transport is enabled - if the migrated group contains any on premises mailboxes the public MX is utilized for routing if the message originated from on premises." 73 | out-logfile -string "If not properly tested this could lead to NDRs or messages appearing as external to on premises resources." 74 | out-logfile -string "The administrator has acknowledged the warning by overriding centralized mail transport enabled." 75 | out-logfile -string $outboundConnector.name 76 | out-logfile -string $outboundConnector.RouteAllMessagesViaOnPremises 77 | out-logfile -string "***WARNING***" 78 | } 79 | else 80 | { 81 | out-logfile -string "Connector not enabled for centralized mail transport." 82 | out-logfile -string $outboundConnector.Name 83 | out-logfile -string $outboundConnector.RouteAllMessagesViaOnPremises 84 | } 85 | } 86 | } 87 | 88 | Out-LogFile -string "END Test-OutboundConnector" 89 | Out-LogFile -string "********************************************************************************" 90 | } 91 | -------------------------------------------------------------------------------- /Test-PowershellVersion.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function ensures the code executes only in Powershell 5.1 until other module dependencies are corrected. 5 | 6 | .DESCRIPTION 7 | 8 | This function ensures the code executes only in Powershell 5.1 until other module dependencies are corrected. 9 | 10 | .EXAMPLE 11 | 12 | Test-PowershellVersion 13 | 14 | #> 15 | Function Test-PowershellVersion 16 | { 17 | [cmdletbinding()] 18 | 19 | $functionPowerShellVersion = $NULL 20 | 21 | Out-LogFile -string "********************************************************************************" 22 | Out-LogFile -string "BEGIN TEST-POWERSHELLVERSION" 23 | Out-LogFile -string "********************************************************************************" 24 | 25 | #Write function parameter information and variables to a log file. 26 | 27 | $functionPowerShellVersion = $PSVersionTable.PSVersion 28 | 29 | out-logfile -string "Determining powershell version." 30 | out-logfile -string ("Major: "+$functionPowerShellVersion.major) 31 | out-logfile -string ("Minor: "+$functionPowerShellVersion.minor) 32 | out-logfile -string ("Patch: "+$functionPowerShellVersion.patch) 33 | out-logfile -string $functionPowerShellVersion 34 | 35 | if ($functionPowerShellVersion.Major -ge 7) 36 | { 37 | out-logfile -string "Powershell 7 and higher is currently not supported due to module compatibility issues." 38 | out-logfile -string "Please run module from Powershell 5.x" 39 | out-logfile -string "" -isError:$true 40 | } 41 | else 42 | { 43 | out-logfile -string "Powershell version is not powershell 7.1 proceed." 44 | } 45 | 46 | Out-LogFile -string "********************************************************************************" 47 | Out-LogFile -string "END TEST-POWERSHELLVERSION" 48 | Out-LogFile -string "********************************************************************************" 49 | 50 | } -------------------------------------------------------------------------------- /convert-O365DLSettingsToOnPremSettings.ps1: -------------------------------------------------------------------------------- 1 | function convert-O365DLSettingsToOnPremSettings 2 | { 3 | 4 | <# 5 | .SYNOPSIS 6 | 7 | This function converts Office 365 Distribution List settings to on premises distribution list LDAP settings for code reuse. 8 | 9 | .DESCRIPTION 10 | 11 | Trigger function. 12 | 13 | .PARAMETER OFFICE365DLCONFIGURATION 14 | 15 | This is the configuration extracted from Office 365 for the group conversion. 16 | 17 | .OUTPUTS 18 | 19 | Returns DL attributes mapped to LDAP attributes. 20 | 21 | .NOTES 22 | 23 | The following blog posts maintain documentation regarding this module. 24 | 25 | https://timmcmic.wordpress.com/2023/01/08/office-365-distribution-list-migration-version-2-0/ 26 | 27 | .EXAMPLE 28 | 29 | convert-o365DLSettingsToOnPremisesSettings -office365DLConfiguration $office365DLConfiguration 30 | 31 | #> 32 | 33 | [CmdletBinding()] 34 | 35 | param ( 36 | [Parameter(Mandatory = $true)] 37 | $office365DLConfiguration 38 | ) 39 | 40 | $functionGroupType = $NULL 41 | $functionCloudSecurity = "MailUniversalSecurityGroup" 42 | $functionADSecurity = "-2147483640" 43 | 44 | #Output all parameters bound or unbound and their associated values. 45 | 46 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 47 | 48 | Out-LogFile -string "********************************************************************************" 49 | Out-LogFile -string "BEGIN convert-O365DLSettingsToOnPremSettings" 50 | Out-LogFile -string "********************************************************************************" 51 | 52 | if ($office365DLConfiguration.recipientType -eq $functionCloudSecurity) 53 | { 54 | out-logfile -string "Group is security type in Office 365 - setting active directory equivalent" 55 | 56 | $functionGroupType = $functionADSecurity 57 | } 58 | else 59 | { 60 | $functionGroupType = "0" 61 | } 62 | 63 | out-logfile -string ("The function group type: "+$functionGroupType) 64 | 65 | $functionObject = New-Object PSObject -Property @{ 66 | msExchEnableModeration=$office365DLConfiguration.ModerationEnabled 67 | msExchHideFromAddressLists=$office365DLConfiguration.HiddenFromAddressListsEnabled 68 | msExchRequireAuthToSendTo=$office365DLConfiguration.RequireSenderAuthenticationEnabled 69 | mailNickName=$office365DLConfiguration.Alias 70 | displayName=$office365DLConfiguration.DisplayName 71 | msExchSenderHintTranslations=$office365DLConfiguration.MailTipTranslations 72 | extensionAttribute1=$office365DLConfiguration.CustomAttribute1 73 | extensionAttribute10=$office365DLConfiguration.CustomAttribute10 74 | extensionAttribute11=$office365DLConfiguration.CustomAttribute11 75 | extensionAttribute12=$office365DLConfiguration.CustomAttribute12 76 | extensionAttribute13=$office365DLConfiguration.CustomAttribute13 77 | extensionAttribute14=$office365DLConfiguration.CustomAttribute14 78 | extensionAttribute15=$office365DLConfiguration.CustomAttribute15 79 | extensionAttribute2=$office365DLConfiguration.CustomAttribute2 80 | extensionAttribute3=$office365DLConfiguration.CustomAttribute3 81 | extensionAttribute4=$office365DLConfiguration.CustomAttribute4 82 | extensionAttribute5=$office365DLConfiguration.CustomAttribute5 83 | extensionAttribute6=$office365DLConfiguration.CustomAttribute6 84 | extensionAttribute7=$office365DLConfiguration.CustomAttribute7 85 | extensionAttribute8=$office365DLConfiguration.CustomAttribute8 86 | extensionAttribute9=$office365DLConfiguration.CustomAttribute9 87 | msExchExtensionCustomAttribute1=$office365DLConfiguration.ExtensionCustomAttribute1 88 | msExchExtensionCustomAttribute2=$office365DLConfiguration.ExtensionCustomAttribute2 89 | msExchExtensionCustomAttribute3=$office365DLConfiguration.ExtensionCustomAttribute3 90 | msExchExtensionCustomAttribute4=$office365DLConfiguration.ExtensionCustomAttribute4 91 | msExchExtensionCustomAttribute5=$office365DLConfiguration.ExtensionCustomAttribute5 92 | proxyAddresses=$office365DLConfiguration.EmailAddresses 93 | mail=$office365DLConfiguration.WindowsEmailAddress 94 | legacyExchangeDN=$office365DLConfiguration.LegacyExchangeDN 95 | groupType=$functionGroupType 96 | msExchRemoteRecipientType="N/A" 97 | msExchRecipientDisplayType=$office365DLConfiguration.RecipientType 98 | msExchRecipientTypeDetails=$office3365DLConfiguration.RecipientTypeDetails 99 | 'msDS-ExternalDirectoryObjectId' = $office365DLConfiguration.externalDirectoryObjectID 100 | distinguishedName = $office365DLConfiguration.distinguishedName 101 | name = $office365DLConfiguration.name 102 | } 103 | 104 | out-logfile -string $functionObject 105 | 106 | Out-LogFile -string "********************************************************************************" 107 | Out-LogFile -string "END convert-O365DLSettingsToOnPremSettings" 108 | Out-LogFile -string "********************************************************************************" 109 | 110 | return $functionObject 111 | } -------------------------------------------------------------------------------- /disable-allPowerShellSessions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function disables all open powershell sessions. 5 | 6 | .DESCRIPTION 7 | 8 | This function disables all open powershell sessions. 9 | 10 | .OUTPUTS 11 | 12 | No return. 13 | 14 | #> 15 | Function disable-allPowerShellSessions 16 | { 17 | 18 | Out-LogFile -string "********************************************************************************" 19 | Out-LogFile -string "BEGIN disable-allPowerShellSessions" 20 | Out-LogFile -string "********************************************************************************" 21 | 22 | out-logfile -string "Determining if the temporary DL should be cleaned up." 23 | 24 | if ($global:DLCleanupInfo -ne $NULL) 25 | { 26 | out-logfile -string "Failure occurred prior to full DL creation in Office 365. Remove temporary DL." 27 | 28 | remove-o365CloudOnlyGroup -office365DLConfiguration $global:DLCleanupInfo -dlCleanupRequired:$TRUE 29 | } 30 | else { 31 | out-logfile -string "Skip temporary DL removal." 32 | } 33 | 34 | out-logfile -string "Determining if the original DL should be moved back to the original OU due to failure." 35 | 36 | if ($global:DLMoveCleanup.originalDLConfiguration -ne $NULL) 37 | { 38 | out-logfile -string "The original DL should be moved back to the original group." 39 | 40 | $tempOUSubstring = Get-OULocation -originalDLConfiguration $global:DLMoveCleanup.originalDLConfiguration -errorAction STOP 41 | 42 | move-toNonSyncOU -OU $tempOUSubstring -dn $global:DLMoveCleanup.originalDLConfiguration.objectGUID -adCredential $global:DLMoveCleanup.adCredential -globalCatalogServer $global:DLMoveCleanup.globalCatalogServer -dlMoveCleanup:$TRUE -errorAction SilentlyContinue 43 | } 44 | else 45 | { 46 | out-logfile -string "Skip moving original DL to original OU." 47 | } 48 | 49 | out-logfile "Gathering all PS Sessions" 50 | 51 | try{ 52 | $functionSessions = Get-PSSession -errorAction STOP 53 | } 54 | catch 55 | { 56 | out-logfile -string "Error getting PSSessions - hard abort since this is called in exit code." 57 | } 58 | 59 | out-logFile -string "Disconnecting Exchange Online Session" 60 | 61 | foreach ($session in $functionSessions) 62 | { 63 | if ($session.computerName -eq "outlook.office365.com") 64 | { 65 | try{ 66 | out-logfile -string $session.id 67 | out-logfile -string $session.name 68 | out-logfile -string $session.computerName 69 | 70 | Disconnect-ExchangeOnline -confirm:$FALSE -errorAction STOP 71 | } 72 | catch{ 73 | out-logfile -string "Error removing Exchange Online Session - Hard Exit since this function is called in error code." 74 | #EXIT 75 | } 76 | } 77 | else 78 | { 79 | out-logfile -string "Removing other non-Exchange Online powershell sessions." 80 | 81 | out-logfile -string $session.id 82 | out-logfile -string $session.name 83 | out-logfile -string $session.computerName 84 | 85 | Get-PSSession | remove-pssession 86 | } 87 | } 88 | 89 | try { 90 | Disconnect-ExchangeOnline -ErrorAction Stop -confirm:$false 91 | } 92 | catch { 93 | out-logfile -string "Error getting PSSessions - hard abort since this is called in exit code." 94 | } 95 | 96 | try { 97 | Disconnect-MgGraph -errorAction STOP 98 | } 99 | catch { 100 | out-logfile -string "Error disconnecting powershell graph - hard abort since this is called in exit code." 101 | } 102 | 103 | out-logfile -string "***IT MAY BE NECESSARY TO EXIT THIS POWERSHELL WINDOW AND REOPEN TO RESTART FROM A FAILED MIGRATION***" 104 | 105 | Out-LogFile -string "END disable-allPowerShellSessions" 106 | Out-LogFile -string "********************************************************************************" 107 | } -------------------------------------------------------------------------------- /enable-ExchangeOnPremEntireForest.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function ensures that view entire forest is set to TRUE for exchange on premsies connections. 5 | 6 | .DESCRIPTION 7 | 8 | This function ensures that view entire forest is set to TRUE for exchange on premsies connections. 9 | 10 | .PARAMETER isAudit 11 | 12 | Specifies if the logging call is terminating / archive files. 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | enable-ExchangeOnPremEntireForest -isAudit:$TRUE 21 | 22 | #> 23 | Function enable-ExchangeOnPremEntireForest 24 | { 25 | Param 26 | ( 27 | [Parameter(Mandatory = $false)] 28 | [boolean]$isAudit=$FALSE 29 | ) 30 | 31 | #Output all parameters bound or unbound and their associated values. 32 | 33 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 34 | 35 | #Declare function variables. 36 | 37 | #Start function processing. 38 | 39 | Out-LogFile -string "********************************************************************************" 40 | Out-LogFile -string "BEGIN enable-ExchangeOnPremEntireForest" 41 | Out-LogFile -string "********************************************************************************" 42 | 43 | try { 44 | out-logfile -string "Attempting to set view entire forest = TRUE." 45 | 46 | Set-ADServerSettings -ViewEntireForest:$TRUE -ErrorAction STOP 47 | } 48 | catch { 49 | out-logfile -string "Unable to set the entire forest settings to true." 50 | out-logfile -string $_ -isError:$TRUE -isAudit $isAudit 51 | } 52 | 53 | Out-LogFile -string "END enable-ExchangeOnPremEntireForest" 54 | Out-LogFile -string "********************************************************************************" 55 | } -------------------------------------------------------------------------------- /get-ActiveDirectoryDomainName.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function utilizes a distinguished domain to calculate an active directory domain name. 5 | 6 | .DESCRIPTION 7 | 8 | This function converts a distinguished name into active directory domain name. 9 | 10 | .PARAMETER DN 11 | 12 | The DN of the object to pass to normalize. 13 | 14 | .OUTPUTS 15 | 16 | The FQDN of the active directory domain. 17 | 18 | .EXAMPLE 19 | 20 | Get-activeDirectoryDomainName -dn $DN 21 | 22 | .CREDITS 23 | 24 | Credit to the following website - code adapted from this location. 25 | http://lanlith.blogspot.com/2014/06/powershell-get-domain-from.html 26 | 27 | #> 28 | Function get-activeDirectoryDomainName 29 | { 30 | [cmdletbinding()] 31 | 32 | Param 33 | ( 34 | [Parameter(Mandatory = $true)] 35 | [string]$DN 36 | ) 37 | 38 | #Output all parameters bound or unbound and their associated values. 39 | 40 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 41 | 42 | #Declare function variables. 43 | 44 | [array]$functionSplitDomainName=@() 45 | [string]$functionCombinedDomainName="" 46 | 47 | #Start function processing. 48 | 49 | Out-LogFile -string "********************************************************************************" 50 | Out-LogFile -string "BEGIN GET-ActiveDirectoryDomainName" 51 | Out-LogFile -string "********************************************************************************" 52 | 53 | #Log the parameters and variables for the function. 54 | 55 | out-logfile -string ("DN to convert: "+$DN) 56 | 57 | Out-LogFile -string "Converting the distinguished name." 58 | 59 | $functionSplitDomainName = $dn -Split "," | ? {$_ -like "DC=*"} 60 | 61 | foreach ($component in $functionSplitDomainName) 62 | { 63 | out-logfile -string $component 64 | } 65 | 66 | $functionCombinedDomainName = $functionSplitDomainName -join "." -replace ("DC=", "") 67 | 68 | out-logfile -string ("The FQDN of the object based on DN: "+$functionCombinedDomainName) 69 | 70 | Out-LogFile -string "END GET-ActiveDirectoryDomainName" 71 | Out-LogFile -string "********************************************************************************" 72 | 73 | #This function is designed to open local and remote powershell sessions. 74 | #If the session requires import - for example exchange - return the session for later work. 75 | #If not no return is required. 76 | 77 | return $functionCombinedDomainName 78 | } -------------------------------------------------------------------------------- /get-ElapsedTime.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function returns the elapsed time between two UTC provided times. 5 | 6 | .DESCRIPTION 7 | 8 | This function returns the elapsed time between two UTC provided times. 9 | 10 | .PARAMETER StartTime 11 | 12 | The start time for evaluation. 13 | 14 | .PARAMETER EndTime 15 | 16 | The end time for evaluation. 17 | 18 | .OUTPUTS 19 | 20 | The difference between the start time and end time. 21 | 22 | .EXAMPLE 23 | 24 | get-elapsedTime -startTime TIME -endTime TIME 25 | 26 | #> 27 | 28 | Function get-elapsedTime 29 | { 30 | [cmdletbinding()] 31 | 32 | Param 33 | ( 34 | [Parameter(Mandatory = $TRUE)] 35 | $startTime, 36 | [Parameter(Mandatory = $TRUE)] 37 | $EndTime 38 | ) 39 | 40 | $functionElapsedTime = ($endTime - $startTime).totalSeconds 41 | 42 | return $functionElapsedTime 43 | } -------------------------------------------------------------------------------- /get-ExchangeSchemaVersion.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function gets the range upper for the exchange schema versions. 5 | 6 | .DESCRIPTION 7 | 8 | This function gets the range upper for the exchange schema versions. 9 | 10 | .OUTPUTS 11 | 12 | Returns the range upper of the Exchange schema versions. 13 | 14 | .EXAMPLE 15 | 16 | get-ExchangeSchemaVersion -globalCatalogServer $GC -adCredential $cred 17 | 18 | #> 19 | Function get-ExchangeSchemaVersion 20 | { 21 | 22 | [cmdletbinding()] 23 | 24 | Param 25 | ( 26 | [Parameter(Mandatory = $true)] 27 | [string]$globalCatalogServer, 28 | [Parameter(Mandatory = $true)] 29 | $adCredential, 30 | [Parameter(Mandatory = $false)] 31 | [ValidateSet("Basic","Negotiate")] 32 | $activeDirectoryAuthenticationMethod="Negotiate" 33 | ) 34 | 35 | out-logfile -string "Output bound parameters..." 36 | 37 | #Output all parameters bound or unbound and their associated values. 38 | 39 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 40 | 41 | Out-LogFile -string "********************************************************************************" 42 | Out-LogFile -string "BEGIN get-exchangeSchemaVersion" 43 | Out-LogFile -string "********************************************************************************" 44 | 45 | out-logfile "Getting the exchange schema version to determine what property set will be cleared during disablment." 46 | 47 | $functionADRootDSE = $null 48 | $functionExchangeSchemaVersion = $null #Exchange schema version detected from AD. 49 | $functionSchemaNamingContext=$null #AD Schema context. 50 | $functionExchangeSchemaContext = $null #Calculated exchange schema location. 51 | $functionExchangeSchemaObject= $null 52 | $functionExchangeRangeUpper = $null 53 | 54 | try{ 55 | $functionADRootDSE=Get-ADRootDSE -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 56 | out-logfile -string "The AD Root Schema:" 57 | out-logfile -string $functionADRootDSE 58 | } 59 | catch 60 | { 61 | out-logfile -string "Unable to get AD Root DSE." 62 | } 63 | 64 | $functionSchemaNamingContext=($functionADRootDSE).SchemaNamingContext 65 | 66 | out-logfile -string ("The functionSchemaNamingContext is :"+$functionSchemaNamingContext) 67 | 68 | $functionExchangeSchemaContext = "CN=ms-Exch-Schema-Version-Pt," + $functionSchemaNamingContext 69 | 70 | out-logfile -string ("The functionExchangeSchemaContext is: "+$functionExchangeSchemaContext) 71 | 72 | try{ 73 | $functionExchangeSchemaObject = Get-AdObject $functionExchangeSchemaContext -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -properties * -errorAction STOP 74 | out-logfile -string ("The Exchange Schema Object is: ") 75 | out-logfile -string $functionExchangeSchemaObject 76 | } 77 | catch{ 78 | out-logfile -string ("Unable to retrieve the Exchange Schema object.") 79 | } 80 | 81 | $functionExchangeRangeUpper = $functionExchangeSchemaObject.rangeUpper 82 | 83 | out-logfile -string ("The range upper of the Exchange Schema: "+$functionExchangeRangeUpper) 84 | 85 | Out-LogFile -string "END get-exchangeSchemaVersion" 86 | Out-LogFile -string "********************************************************************************" 87 | 88 | return $functionExchangeRangeUpper 89 | } -------------------------------------------------------------------------------- /get-OULocation.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function calculates the correct OU to place an object. 5 | 6 | .DESCRIPTION 7 | 8 | This function calculates the correct OU to place an object. 9 | 10 | .PARAMETER originalDLConfiguration 11 | 12 | The mail attribute of the group to search. 13 | 14 | .OUTPUTS 15 | 16 | Returns the organizational unit where the object should be stored. 17 | 18 | .EXAMPLE 19 | 20 | get-OULocation -originalDLConfiguration $originalDLConfiguration 21 | 22 | #> 23 | 24 | Function Get-OULocation 25 | { 26 | [cmdletbinding()] 27 | 28 | Param 29 | ( 30 | [Parameter(Mandatory = $true)] 31 | $originalDLConfiguration 32 | ) 33 | 34 | #Output all parameters bound or unbound and their associated values. 35 | 36 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 37 | 38 | Out-LogFile -string "********************************************************************************" 39 | Out-LogFile -string "BEGIN Get-OULocation" 40 | Out-LogFile -string "********************************************************************************" 41 | 42 | #Declare function variables. 43 | 44 | [string]$returnOU=$NULL 45 | 46 | #Test to see if the DN contains an OU. 47 | 48 | out-logfile -string $originalDLConfiguration.distinguishedname 49 | 50 | $testOUSubstringLocation = $originalDLConfiguration.distinguishedName.indexof(",OU=") 51 | out-logfile -string ("The location of ,OU= is:"+$testOUSubstringLocation) 52 | 53 | if ($testOUSubStringLocation -ge 0) 54 | { 55 | out-logfile -string "The group is in an organizational unit." 56 | out-logfile -string $testOUSubstringLocation.tostring() 57 | $tempOUSubstring = $originalDLConfiguration.distinguishedname.substring($testOUSubstringLocation) 58 | out-logfile -string "Temp OU Substring = " 59 | out-logfile -string $tempOUSubstring 60 | $testOUSubstringLocation = $tempOUSubstring.indexof("OU=") 61 | out-logfile -string $testOUSubstringLocation.tostring() 62 | $tempOUSubstring = $tempOUSubstring.substring($testOUSubstringLocation) 63 | out-logfile -string "Temp OU Substring Substring =" 64 | out-logfile -string $tempOUSubstring 65 | } 66 | else 67 | { 68 | out-logfile -string "The group is in a container and not an OU." 69 | $testOUSubstringLocation = $originalDLConfiguration.distinguishedName.indexof(",CN=") 70 | out-logfile -string $testOUSubstringLocation.tostring() 71 | $tempOUSubstring = $originalDLConfiguration.distinguishedname.substring($testOUSubstringLocation) 72 | out-logfile -string "Temp OU Substring = " 73 | out-logfile -string $tempOUSubstring 74 | $testOUSubstringLocation = $tempOUSubstring.indexof("CN=") 75 | out-logfile -string $testOUSubstringLocation.tostring() 76 | $tempOUSubstring = $tempOUSubstring.substring($testOUSubstringLocation) 77 | out-logfile -string "Temp OU Substring Substring =" 78 | out-logfile -string $tempOUSubstring 79 | } 80 | 81 | $returnOU = $tempOUSubstring 82 | 83 | Out-LogFile -string "********************************************************************************" 84 | Out-LogFile -string "END Get-OULocation" 85 | Out-LogFile -string "********************************************************************************" 86 | 87 | return $returnOU 88 | } -------------------------------------------------------------------------------- /get-StatusFileCount.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function returns the file count of the status directory. 5 | 6 | .DESCRIPTION 7 | 8 | This function returns the count of the status file directory. 9 | 10 | .OUTPUTS 11 | 12 | Count of status directory. 13 | 14 | .EXAMPLE 15 | 16 | get-StatusFileCount 17 | 18 | #> 19 | Function get-statusFileCount 20 | { 21 | out-logfile -string "================================================================================" 22 | out-logfile -string "START Get-StatusFileCount" 23 | out-logfile -string "================================================================================" 24 | 25 | [int]$functionFileCount = 0 26 | [array]$childItems=@() 27 | 28 | try{ 29 | $childItems=get-childitem -path $global:fullStatusPath -file -errorAction STOP 30 | } 31 | catch{ 32 | out-logfile -string "Unable to get count of files in status directory." -isError:$TRUE 33 | } 34 | 35 | $functionFileCount = $childItems.count 36 | 37 | if ($functionFileCount -gt 0) 38 | { 39 | out-logfile -string "The child items found in the status directory." 40 | out-logfile -string $childItems 41 | 42 | out-logfile -string "The number of items found in the status directory." 43 | out-logfile -string $functionFileCount 44 | } 45 | else 46 | { 47 | out-logfile -string "No files found in directory." 48 | } 49 | 50 | out-logfile -string "================================================================================" 51 | out-logfile -string "END Get-StatusFileCount" 52 | out-logfile -string "================================================================================" 53 | 54 | return $functionFileCount 55 | } -------------------------------------------------------------------------------- /get-UniversalDateTime.ps1: -------------------------------------------------------------------------------- 1 | function get-universalDateTime 2 | { 3 | $functionUniversalDateTime = (get-date).toUniversalTime() 4 | 5 | return $functionUniversalDateTIme 6 | } -------------------------------------------------------------------------------- /get-distinguishedName.ps1: -------------------------------------------------------------------------------- 1 | #All credits to author - link below. Code modified by publisher. 2 | #https://millerb.co.uk/2019/07/16/Get-DistinguishedName-From-CanonicalName.html 3 | 4 | 5 | function Get-DistinguishedName { 6 | param ( 7 | [Parameter(Mandatory)] 8 | [string]$CanonicalName 9 | ) 10 | 11 | #Output all parameters bound or unbound and their associated values. 12 | 13 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 14 | 15 | [string]$returnDN = "" 16 | 17 | Out-LogFile -string "********************************************************************************" 18 | Out-LogFile -string "BEGIN GET-DistinguishedName" 19 | Out-LogFile -string "********************************************************************************" 20 | 21 | foreach ($cn in $CanonicalName) 22 | { 23 | $arr = $cn -split '/' 24 | [array]::reverse($arr) 25 | $output = @() 26 | $output += $arr[0] -replace '^.*$', 'CN=$0' 27 | $output += ($arr | select -Skip 1 | select -SkipLast 1) -replace '^.*$', 'OU=$0' 28 | $output += ($arr[$arr.count-1] | ? { $_ -like '*.*' }) -split '\.' -replace '^.*$', 'DC=$0' 29 | $returnDN = $output -join ',' 30 | } 31 | 32 | out-logfile -string ("Converted canonical name: "+$returnDN) 33 | 34 | Out-LogFile -string "********************************************************************************" 35 | Out-LogFile -string "END Get-DistinguishedName" 36 | Out-LogFile -string "********************************************************************************" 37 | 38 | return $returnDN 39 | } -------------------------------------------------------------------------------- /get-mailOnMicrosoftComDomain.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function determines the hybrid mail.onmicrosoft.com domain name. 5 | This function is required to support additions of onmicrosoft.com domain names which can be used as addresses but not for routing. 6 | 7 | .DESCRIPTION 8 | 9 | This function determines the hybrid mail.onmicrosoft.com domain name. 10 | 11 | .EXAMPLE 12 | 13 | Get-MailOnMicrosoftComDomain 14 | 15 | #> 16 | Function Get-MailOnMicrosoftComDomain 17 | { 18 | [cmdletbinding()] 19 | 20 | #Define variables that will be utilzed in the function. 21 | 22 | [string]$functionDomainName = "" 23 | [array]$functionAcceptedDomains = @() 24 | [string]$functionDomainString0 = "*.mail.onmicrosoft.com" 25 | [string]$functionDomainString1 = "*.microsoftonline.com" 26 | 27 | #Initiate the test. 28 | 29 | Out-LogFile -string "********************************************************************************" 30 | Out-LogFile -string "BEGIN Get-MailOnMicrosoftComDomain" 31 | Out-LogFile -string "********************************************************************************" 32 | 33 | try{ 34 | $functionAcceptedDomains = get-o365acceptedDomain -errorAction STOP 35 | } 36 | catch{ 37 | out-logfile -string $_ 38 | out-logfile -string "Error obtaining accepted domains." -isError:$TRUE 39 | } 40 | 41 | <# 42 | Commenting out the original code. 43 | 44 | Encountered a customer issue where they have not online a mail.onmicrosoft.com domain but also the legacy microsoftonline.com domain encountered in other situations. 45 | 46 | If both are present we should always prefer the onmicrosoft.com domain as it is the modern routing domain. 47 | 48 | foreach ($domain in $functionAcceptedDomains) 49 | { 50 | out-logfile -string ("Testing Domain: "+$domain.domainName) 51 | 52 | if ($domain.domainName.contains($functionDomainString0)) 53 | { 54 | out-logfile -string ("Mail.onmicrosoft.com domain name found: "+$domain.domainName) 55 | $functionDomainName = $domain.domainName 56 | } 57 | elseif ($domain.domainName.contains($functionDomainString1)) 58 | { 59 | out-logfile -string ("Legacy microsoft online domain name found: "+$domain.domainName) 60 | $functionDomainName = $domain.domainName 61 | } 62 | else 63 | { 64 | out-logfile -string ("Domain is not mail.onmicrosoft.com: "+$domain.domainName) 65 | } 66 | } 67 | 68 | #> 69 | 70 | if ($functionDomainName = ($functionAcceptedDomains.where({$_.domainName -like $functionDomainString0})).domainName) 71 | { 72 | out-logfile -string "Onmicrosoft.com routing domain identified." 73 | out-logfile -string $functionDomainName 74 | } 75 | elseif ($functionDomainName = ($functionAcceptedDomains.where({$_.domainName -like $functionDomainString1})).domainName) 76 | { 77 | out-logfile -string "MicrosoftOnline routing domain identified." 78 | out-logfile -string $functionDomainName 79 | } 80 | else 81 | { 82 | out-logfile -string "No viable mail routing address was found." 83 | out-logfile -string "Contact support or post an issue on GITHUB." -isError:$true 84 | } 85 | 86 | Out-LogFile -string "END Get-MailOnMicrosoftComDomain" 87 | Out-LogFile -string "********************************************************************************" 88 | 89 | return $functionDomainName 90 | } -------------------------------------------------------------------------------- /get-msGraphDLConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function gathers the group information from Azure Active Directory. 5 | 6 | .DESCRIPTION 7 | 8 | This function gathers the group information from Azure Active Directory. 9 | 10 | .PARAMETER office365DLConfiguration 11 | 12 | The Office 365 DL configuration for the group. 13 | 14 | .OUTPUTS 15 | 16 | Returns the information from the associated group from Azure AD> 17 | 18 | .EXAMPLE 19 | 20 | get-AzureADDLConfiguration -office365DLConfiguration $configuration 21 | 22 | #> 23 | Function get-msGraphDLConfiguration 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | $office365DLConfiguration 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | #Start function processing. 38 | 39 | Out-LogFile -string "********************************************************************************" 40 | Out-LogFile -string "BEGIN GET-AZUREADDLCONFIGURATION" 41 | Out-LogFile -string "********************************************************************************" 42 | 43 | #Get the recipient using the exchange online powershell session. 44 | 45 | try{ 46 | $functionDLConfiguration = get-mgGroup -groupID $office365DLConfiguration.externalDirectoryObjectID -errorAction STOP 47 | } 48 | catch { 49 | out-logfile -string $_ 50 | out-logfile -string "Unable to obtain group configuration from Azure Active Directory" 51 | } 52 | 53 | Out-LogFile -string "END GET-AzureADDlConfiguration" 54 | Out-LogFile -string "********************************************************************************" 55 | 56 | #This function is designed to open local and remote powershell sessions. 57 | #If the session requires import - for example exchange - return the session for later work. 58 | #If not no return is required. 59 | 60 | return $functionDLConfiguration 61 | } -------------------------------------------------------------------------------- /get-onPremFolderPermissions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function utilizes the collected data to search for mailbox folder permissions on the migrated DL. 5 | 6 | .DESCRIPTION 7 | 8 | This function utilizes the collected data to search for mailbox folder permissions on the migrated DL. 9 | 10 | .PARAMETER originalDLConfiguration 11 | 12 | The mail attribute of the group to search. 13 | 14 | .PARAMETER collectedData 15 | 16 | .OUTPUTS 17 | 18 | Returns all default or user created mailbox folder permissions. 19 | 20 | .EXAMPLE 21 | 22 | get-o365dlconfiguration -groupSMTPAddress Address -collectedData Data 23 | 24 | #> 25 | Function get-onPremFolderPermissions 26 | { 27 | [cmdletbinding()] 28 | 29 | Param 30 | ( 31 | [Parameter(Mandatory = $true)] 32 | $originalDLConfiguration, 33 | [Parameter(Mandatory=$false)] 34 | $collectedData=$NULL 35 | ) 36 | 37 | #Output all parameters bound or unbound and their associated values. 38 | 39 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 40 | 41 | #Declare function variables. 42 | 43 | [array]$functionFolderRightsUsers=@() 44 | [int]$functionCounter=0 45 | 46 | Out-LogFile -string "********************************************************************************" 47 | Out-LogFile -string "BEGIN get-onPremFolderPermissions" 48 | Out-LogFile -string "********************************************************************************" 49 | 50 | 51 | out-logfile -string "Test for folder permissions." 52 | 53 | out-logfile -string "Filter all permissions for objects that are no longer valid" 54 | out-logfile -string ("Pre collected data count: "+$collectedData.count) 55 | 56 | $collectedData = $collectedData | where {$_.user.adrecipient -ne $NULL} 57 | 58 | out-logfile -string ("Post collected data count: "+$collecteddata.count) 59 | 60 | $functionFolderRightsUsers = $collectedData | where {($_.user.ADRecipient.primarySMTpAddress).tolower().contains($originalDLConfiguration.mail.toLower())} 61 | 62 | Out-LogFile -string "********************************************************************************" 63 | Out-LogFile -string "END get-onPremFolderPermissions" 64 | Out-LogFile -string "********************************************************************************" 65 | 66 | if ($functionFolderRightsUsers.count -gt 0) 67 | { 68 | out-logfile -string $functionFolderRightsUsers 69 | return $functionFolderRightsUsers 70 | } 71 | } -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011 Timothy J. McMichael / Microsoft CSS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /move-toNonSyncOU.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function moves the group to the non-SYNC OU. This is necessary to process the group deletion from Office 365. 5 | 6 | .DESCRIPTION 7 | 8 | This function moves the group to the non-SYNC OU. This is necessary to process the group deletion from Office 365. 9 | 10 | .PARAMETER GlobalCatalogServer 11 | 12 | The global catalog to make the query against. 13 | 14 | .PARAMETER DN 15 | 16 | The original DN of the object. 17 | 18 | .PARAMETER OU 19 | 20 | This is the OU that is set to not synchonize in AD Connect. 21 | 22 | .PARAMETER adCredential 23 | 24 | This is the credential for active directory operations. 25 | 26 | .OUTPUTS 27 | 28 | None 29 | 30 | .EXAMPLE 31 | 32 | move-toNonSyncOU -globalCatalogServer GC -OU NonSyncOU -DN groupDN -adCredential CRED 33 | 34 | #> 35 | Function move-toNonSyncOU 36 | { 37 | [cmdletbinding()] 38 | 39 | Param 40 | ( 41 | [Parameter(Mandatory = $true)] 42 | [string]$globalCatalogServer, 43 | [Parameter(Mandatory = $true)] 44 | $OU, 45 | [Parameter(Mandatory = $true)] 46 | $DN, 47 | [Parameter(Mandatory = $true)] 48 | $adCredential, 49 | [Parameter(Mandatory = $false)] 50 | [ValidateSet("Basic","Negotiate")] 51 | $activeDirectoryAuthenticationMethod="Negotiate", 52 | [Parameter(Mandatory = $false)] 53 | $dlMoveCleanup=$FALSE, 54 | [Parameter(Mandatory = $false)] 55 | $dlPostCreate=$FALSE 56 | ) 57 | 58 | #Output all parameters bound or unbound and their associated values. 59 | 60 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 61 | 62 | #Declare function variables. 63 | 64 | #Start function processing. 65 | 66 | Out-LogFile -string "********************************************************************************" 67 | Out-LogFile -string "START MOVE-TONONSYNCOU" 68 | Out-LogFile -string "********************************************************************************" 69 | 70 | [boolean]$stopLoop=$false 71 | [int]$loopCounter = 0 72 | 73 | if ($dlMoveCleanup -eq $FALSE) 74 | { 75 | if ($dlPostCreate -eq $FALSE) 76 | { 77 | do 78 | { 79 | Out-LogFile -string "Move the group to the non-SYNC OU..." 80 | 81 | try { 82 | move-adObject -identity $DN -targetPath $OU -credential $adCredential -server $globalCatalogServer -authType $activeDirectoryAuthenticationMethod -errorAction Stop 83 | 84 | $stopLoop = $true 85 | } 86 | catch { 87 | if ($loopCounter -lt 5) 88 | { 89 | out-logfile -string "Attempt to move to non-sync OU failed - wait and retry." 90 | out-logfile -string ("Attempt number: "+$loopcounter.tostring()) 91 | 92 | $loopCounter++ 93 | 94 | start-sleepProgress -sleepSeconds 5 -sleepString "Attempt to move to non-sync OU failed - sleep 5 seconds retry." 95 | } 96 | else { 97 | out-logfile -string "Unable to move the group to a non-sync OU - abandon the move." 98 | out-logfile -string $_ -isError:$true 99 | } 100 | } 101 | } until ($stopLoop -eq $TRUE) 102 | } 103 | else 104 | { 105 | try { 106 | move-adObject -identity $DN -targetPath $OU -credential $adCredential -server $globalCatalogServer -authType $activeDirectoryAuthenticationMethod -errorAction Stop 107 | } 108 | catch { 109 | out-logfile -string "Unable to move the group between organizational units. Manual intervention required." 110 | 111 | $isErrorObject = new-Object psObject -property @{ 112 | PrimarySMTPAddressorUPN = "" 113 | ExternalDirectoryObjectID = "" 114 | Alias = "" 115 | Name = $DN 116 | Attribute = "" 117 | ErrorMessage = "Unable to move the on premises group between OUs. Manual administrator intervention required." 118 | ErrorMessageDetail = $_ 119 | } 120 | 121 | out-logfile -string $isErrorObject 122 | 123 | $global:postCreateErrors += $isErrorObject 124 | } 125 | } 126 | } 127 | else 128 | { 129 | out-logfile -string "Attempting one move back to the source OU - on premises group was moved to no-sync and failure occurred." 130 | 131 | move-adObject -identity $DN -targetPath $OU -credential $adCredential -server $globalCatalogServer -authType $activeDirectoryAuthenticationMethod -errorAction SilentlyContinue 132 | } 133 | 134 | Out-LogFile -string "END MOVE-TONONSYNCOU" 135 | Out-LogFile -string "********************************************************************************" 136 | } -------------------------------------------------------------------------------- /new-AzureADPowershellSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function creates the powershell session to Azure AD. 5 | 6 | .DESCRIPTION 7 | 8 | This function creates the powershell session to Azure AD. 9 | 10 | .PARAMETER azureADCredential 11 | 12 | The credential utilized to connect to azure ad. 13 | 14 | .PARAMETER azureCertificateThumbprint 15 | 16 | The certificate thumbprint for the associated azure application. 17 | 18 | .PARAMETER azureTenantID 19 | 20 | The tenant ID associated with the azure application. 21 | 22 | .PARAMETER azureApplicationID 23 | 24 | The application ID for azure management. 25 | 26 | .PARAMETER azureEnvironmentName 27 | 28 | The azure environment for the connection to azure ad. 29 | 30 | .OUTPUTS 31 | 32 | Powershell session to use for exchange online commands. 33 | 34 | .EXAMPLE 35 | 36 | new-AzureADPowershellSession -AzureADCredential $CRED -azureEnvironmentName NAME 37 | 38 | #> 39 | Function New-AzureADPowershellSession 40 | { 41 | [cmdletbinding()] 42 | 43 | Param 44 | ( 45 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $false)] 46 | [pscredential]$azureADCredential=$NULL, 47 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 48 | [string]$azureCertificateThumbPrint, 49 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 50 | [string]$azureTenantID, 51 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 52 | [string]$azureApplicationID, 53 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $true)] 54 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 55 | [string]$azureEnvironmentName, 56 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $false)] 57 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $false)] 58 | [boolean]$isAudit=$FALSE 59 | ) 60 | 61 | #Output all parameters bound or unbound and their associated values. 62 | 63 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 64 | 65 | #Define variables that will be utilzed in the function. 66 | 67 | [boolean]$isCertAuth=$false 68 | #$exchangeOnlineCommands=@('get-ExoRecipient','new-distributionGroup','get-recipient','set-distributionGroup','get-distributionGroupMember','get-mailbox','get-unifiedGroup','set-UnifiedGroup') 69 | #Initiate the session. 70 | 71 | Out-LogFile -string "********************************************************************************" 72 | Out-LogFile -string "BEGIN NEW-AzureADPowershellSession" 73 | Out-LogFile -string "********************************************************************************" 74 | 75 | if ($azureCertificateThumbPrint -ne "") 76 | { 77 | $isCertAuth=$true 78 | out-logfile -string ("Is certificate auth = "+$isCertAuth) 79 | } 80 | 81 | if ($isCertAuth -eq $False) 82 | { 83 | if ($azureADCredential -ne $NULL) 84 | { 85 | try 86 | { 87 | Out-LogFile -string "Creating the azure active directory powershell session." 88 | 89 | Connect-AzureAD -Credential $azureADCredential -azureEnvironmentName $azureEnvironmentName 90 | } 91 | catch 92 | { 93 | Out-LogFile -string $_ -isError:$TRUE -isAudit $isAudit 94 | } 95 | } 96 | else 97 | { 98 | try 99 | { 100 | Out-LogFile -string "Creating the azure active directory powershell session." 101 | 102 | Connect-AzureAD -azureEnvironmentName $azureEnvironmentName 103 | } 104 | catch 105 | { 106 | Out-LogFile -string $_ -isError:$TRUE -isAudit $isAudit 107 | } 108 | } 109 | } 110 | elseif ($isCertAuth -eq $TRUE) 111 | { 112 | try 113 | { 114 | out-logfile -string "Creating the connection to exchange online powershell using certificate authentication." 115 | 116 | connect-AzureAD -certificateThumbPrint $azureCertificateThumbPrint -applicationID $azureApplicationID -tenantID $azureTenantID -azureEnvironmentName $azureEnvironmentName 117 | } 118 | catch 119 | { 120 | out-logfile -string $_ -isError:$TRUE -isAudit $isAudit 121 | } 122 | } 123 | 124 | Out-LogFile -string "The exchange online powershell session was created successfully." 125 | 126 | Out-LogFile -string "END NEW-AZUREADPOWERSHELL SESSION" 127 | Out-LogFile -string "********************************************************************************" 128 | } -------------------------------------------------------------------------------- /new-MSGraphPowershellSession.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function creates the powershell session to msGraph AD. 5 | 6 | .DESCRIPTION 7 | 8 | This function creates the powershell session to msGraph AD. 9 | 10 | .PARAMETER msGraphADCredential 11 | 12 | The credential utilized to connect to msGraph ad. 13 | 14 | .PARAMETER msGraphCertificateThumbprint 15 | 16 | The certificate thumbprint for the associated msGraph application. 17 | 18 | .PARAMETER msGraphTenantID 19 | 20 | The tenant ID associated with the msGraph application. 21 | 22 | .PARAMETER msGraphApplicationID 23 | 24 | The application ID for msGraph management. 25 | 26 | .PARAMETER msGraphEnvironmentName 27 | 28 | The msGraph environment for the connection to msGraph ad. 29 | 30 | .OUTPUTS 31 | 32 | Powershell session to use for exchange online commands. 33 | 34 | .EXAMPLE 35 | 36 | new-msGraphADPowershellSession -msGraphADCredential $CRED -msGraphEnvironmentName NAME 37 | 38 | #> 39 | Function New-MSGraphPowershellSession 40 | { 41 | [cmdletbinding()] 42 | 43 | Param 44 | ( 45 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 46 | [string]$msGraphCertificateThumbPrint="", 47 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 48 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $true)] 49 | [string]$msGraphTenantID, 50 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 51 | [string]$msGraphApplicationID, 52 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $true)] 53 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 54 | [string]$msGraphEnvironmentName, 55 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $false)] 56 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $false)] 57 | [boolean]$isAudit=$FALSE, 58 | [Parameter(ParameterSetName = "CertificateCredentials",Mandatory = $true)] 59 | [Parameter(ParameterSetName = "UserCredentials",Mandatory = $true)] 60 | [array]$msGraphScopesRequired=@() 61 | ) 62 | 63 | #Output all parameters bound or unbound and their associated values. 64 | 65 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 66 | 67 | #Define variables that will be utilzed in the function. 68 | 69 | [boolean]$isCertAuth=$false 70 | #$exchangeOnlineCommands=@('get-ExoRecipient','new-distributionGroup','get-recipient','set-distributionGroup','get-distributionGroupMember','get-mailbox','get-unifiedGroup','set-UnifiedGroup') 71 | #Initiate the session. 72 | 73 | Out-LogFile -string "********************************************************************************" 74 | Out-LogFile -string "BEGIN NEW-msGraphADPowershellSession" 75 | Out-LogFile -string "********************************************************************************" 76 | 77 | if ($msGraphCertificateThumbPrint -ne "") 78 | { 79 | $isCertAuth=$true 80 | out-logfile -string ("Is certificate auth = "+$isCertAuth) 81 | } 82 | 83 | if ($isCertAuth -eq $False) 84 | { 85 | out-logfile -string "Making MS Graph connection using interactive credentials." 86 | 87 | try { 88 | connect-mgGraph -tenantID $msGraphTenantID -environment $msGraphEnvironmentName -scopes $msGraphScopesRequired -errorAction STOP 89 | } 90 | catch { 91 | out-logfile -string "Unable to make ms graph connection using interactive authentication." 92 | out-logfile -string "https://timmcmic.wordpress.com/2023/04/11/office-365-distribution-list-migrations-version-2-0-part-33/" 93 | out-logfile $_ -isError:$TRUE 94 | } 95 | } 96 | elseif ($isCertAuth -eq $TRUE) 97 | { 98 | try 99 | { 100 | out-logfile -string "Creating the connection to exchange online powershell using certificate authentication." 101 | 102 | connect-mgGraph -certificateThumbprint $msGraphCertificateThumbPrint -ClientId $msGraphApplicationID -tenantID $msGraphTenantID -environment $msGraphEnvironmentName 103 | } 104 | catch 105 | { 106 | out-logfile -string "Unable to connect to graph using certificate authentication." 107 | out-logfile -string "https://timmcmic.wordpress.com/2023/04/11/office-365-distribution-list-migrations-version-2-0-part-33/" 108 | out-logfile -string $_ -isError:$TRUE -isAudit $isAudit 109 | } 110 | } 111 | 112 | Out-LogFile -string "The MS Graph powershell session was created successfully." 113 | 114 | out-logfile -string (Get-MgContext) 115 | 116 | Out-LogFile -string "END NEW-msGraphADPOWERSHELL SESSION" 117 | Out-LogFile -string "********************************************************************************" 118 | } 119 | -------------------------------------------------------------------------------- /new-Office365DL.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function creates the new distribution group in office 365. 5 | 6 | .DESCRIPTION 7 | 8 | This function creates the new distribution group in office 365. 9 | 10 | .PARAMETER originalDLConfiguration 11 | 12 | The original configuration of the DL on premises. 13 | 14 | .PARAMETER groupTypeOverride 15 | 16 | Submits the group type override of specified by the administrator at run time. 17 | 18 | .OUTPUTS 19 | 20 | None 21 | 22 | .EXAMPLE 23 | 24 | new-Office365DL -groupTypeOverride "Security" -originalDLConfiguration adConfigVariable -office365DLConfiguration CONFIG 25 | 26 | #> 27 | Function new-office365dl 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | $originalDLConfiguration, 35 | [Parameter(Mandatory = $true)] 36 | $office365DLConfiguration, 37 | [Parameter(Mandatory = $true)] 38 | [string]$groupTypeOverride 39 | ) 40 | 41 | #Output all parameters bound or unbound and their associated values. 42 | 43 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 44 | 45 | #Declare function variables. 46 | 47 | [string]$functionGroupType=$NULL #Holds the return information for the group query. 48 | [string]$functionMailNickName = "" 49 | [string]$functionName = ((Get-Date -Format FileDateTime)+(Get-Random)).tostring() 50 | $functionDL = $NULL 51 | $functionIsRoom = $FALSE 52 | 53 | #Start function processing. 54 | 55 | Out-LogFile -string "********************************************************************************" 56 | Out-LogFile -string "BEGIN New-Office365DL" 57 | Out-LogFile -string "********************************************************************************" 58 | 59 | #Calculate the group type to be utilized. 60 | #Three values - either NULL,Security,or Distribution. 61 | 62 | out-Logfile -string ("The group type for evaluation is = "+$originalDLConfiguration.groupType) 63 | 64 | if ($groupTypeOverride -Eq "Security") 65 | { 66 | out-logfile -string "The administrator overrode the group type to security." 67 | 68 | $functionGroupType = "Security" 69 | } 70 | elseif ($groupTypeOverride -eq "Distribution") 71 | { 72 | out-logfile -string "The administrator overrode the group type to distribution." 73 | 74 | $functionGroupType = "Distribution" 75 | } 76 | elseif ($groupTypeOverride -eq "None") 77 | { 78 | out-logfile -string "A group type override was not specified. Using group type from on premises." 79 | 80 | if (($originalDLConfiguration.groupType -eq "-2147483640") -or ($originalDLConfiguration.groupType -eq "-2147483646") -or ($originalDLConfiguration.groupType -eq "-2147483644")) 81 | { 82 | out-logfile -string "The group type from on premises is security." 83 | 84 | $functionGroupType = "Security" 85 | } 86 | elseif (($originalDLConfiguration.grouptype -eq "8") -or ($originalDLConfiguration.grouptype -eq "4") -or ($originalDLConfiguration.grouptype -eq "2")) 87 | { 88 | out-logfile -string "The group type from on premises is distribution." 89 | 90 | $functionGroupType = "Distribution" 91 | } 92 | else 93 | { 94 | out-logfile -string "A group type override was not provided and the input did not include a valid on premises group type." 95 | } 96 | } 97 | else 98 | { 99 | out-logfile -string "An invalid group type was utilized in function new-Office365DL" -isError:$TRUE 100 | } 101 | 102 | out-logfile -string ("Random DL name: "+$functionName) 103 | 104 | #Test to determine if the distribution group is a room distribution group. 105 | 106 | if ($originalDLConfiguration.msExchRecipientTypeDetails -eq "268435456") 107 | { 108 | out-logfile -string "The group is a room distribution list." 109 | $functionIsRoom = $TRUE 110 | } 111 | else 112 | { 113 | out-logfile -string "The group is not a room distribution list." 114 | } 115 | 116 | #Create the distribution group in office 365. 117 | 118 | try 119 | { 120 | if ($functionIsRoom -eq $FALSE) 121 | { 122 | out-logfile -string "Creating the distribution group in Office 365." 123 | 124 | $functionDL = new-o365distributionGroup -name $functionName -type $functionGroupType -ignoreNamingPolicy:$TRUE -errorAction STOP 125 | 126 | out-logfile -string $functionDL 127 | } 128 | else 129 | { 130 | out-logfile -string "Creating the distribution group in Office 365 as a room list." 131 | 132 | $functionDL = new-o365distributionGroup -name $functionName -type $functionGroupType -ignoreNamingPolicy:$TRUE -roomList -errorAction STOP 133 | 134 | out-logfile -string $functionDL 135 | } 136 | } 137 | catch 138 | { 139 | Out-LogFile -string $_ -isError:$TRUE 140 | } 141 | 142 | Out-LogFile -string "END New-Office365DL" 143 | Out-LogFile -string "********************************************************************************" 144 | 145 | return $functionDL 146 | } -------------------------------------------------------------------------------- /new-Office365Group.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function creates the new distribution group in office 365. 5 | 6 | .DESCRIPTION 7 | 8 | This function creates the new distribution group in office 365. 9 | 10 | .PARAMETER originalDLConfiguration 11 | 12 | The original configuration of the DL on premises. 13 | 14 | .PARAMETER groupTypeOverride 15 | 16 | Submits the group type override of specified by the administrator at run time. 17 | 18 | .OUTPUTS 19 | 20 | None 21 | 22 | .EXAMPLE 23 | 24 | new-Office365DL -groupTypeOverride "Security" -originalDLConfiguration adConfigVariable -office365DLConfiguration CONFIG 25 | 26 | #> 27 | Function new-office365Group 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | $originalDLConfiguration, 35 | [Parameter(Mandatory = $true)] 36 | $office365DLConfiguration, 37 | [Parameter(Mandatory = $true)] 38 | $exchangeOnlineConnectionInfo 39 | ) 40 | 41 | #Output all parameters bound or unbound and their associated values. 42 | 43 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 44 | 45 | #Declare function variables. 46 | 47 | [string]$functionGroupType=$NULL #Holds the return information for the group query. 48 | [string]$functionMailNickName = "" 49 | [string]$functionName = ((Get-Date -Format FileDateTime)+(Get-Random)).tostring() 50 | $functionDL = $NULL 51 | $functionIsRoom = $FALSE 52 | 53 | #Start function processing. 54 | 55 | Out-LogFile -string "********************************************************************************" 56 | Out-LogFile -string "BEGIN New-Office365Group" 57 | Out-LogFile -string "********************************************************************************" 58 | 59 | out-logfile -string ("Random DL name: "+$functionName) 60 | 61 | #Create the distribution group in office 365. 62 | 63 | try 64 | { 65 | out-logfile -string "Creating the distribution group in Office 365." 66 | 67 | $previousErrorAction = $ErrorActionPreference 68 | $ErrorActionPreference = 'Stop' 69 | 70 | $functionDL = new-o365UnifiedGroup -displayname $functionName -owner $exchangeOnlineConnectionInfo.userPrincipalName 71 | 72 | $ErrorActionPreference = $previousErrorAction 73 | 74 | out-logfile -string $functionDL 75 | } 76 | catch 77 | { 78 | Out-LogFile -string $_ -isError:$TRUE 79 | } 80 | 81 | Out-LogFile -string "END New-Office365Group" 82 | Out-LogFile -string "********************************************************************************" 83 | 84 | return $functionDL 85 | } -------------------------------------------------------------------------------- /out-StatusFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function writes the status file for migrations. 5 | 6 | .DESCRIPTION 7 | 8 | Logging 9 | 10 | .PARAMETER threadNumber 11 | 12 | Thread number for the associated status file. 13 | 14 | .OUTPUTS 15 | 16 | Creates a text file to allow for tracking multi-threaded operations. 17 | 18 | .EXAMPLE 19 | 20 | Out-StatusFile -threadNumber 0 21 | 22 | #> 23 | Function Out-StatusFile 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $false)] 30 | [int]$threadNumber=0 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | Out-LogFile -string "********************************************************************************" 38 | Out-LogFile -string "BEGIN OUT-STATUSFILE" 39 | Out-LogFile -string "********************************************************************************" 40 | 41 | #Define the status file. 42 | 43 | [array]$threadStatus="ThreadZeroStatus.txt","ThreadOneStatus.txt","ThreadTwoStatus.txt","ThreadThreeStatus.txt","ThreadFourStatus.txt","ThreadFiveStatus.txt","ThreadSixStatus.txt","ThreadSevenStatus.txt","ThreadEightStatus.txt","ThreadNineStatus.txt","ThreadTenStatus.txt" 44 | 45 | [string]$statusString="DONE" 46 | 47 | [String]$functionStatus = Join-path $global:fullStatusPath $threadStatus[$threadNumber] 48 | 49 | out-logFile -string $functionStatus 50 | 51 | #Write the generic thread to the file - we only care that the file was created. 52 | 53 | try 54 | { 55 | $statusString | Out-File -FilePath $functionStatus -force 56 | } 57 | catch 58 | { 59 | out-logfile $_ -isError:$TRUE 60 | } 61 | 62 | Out-LogFile -string "********************************************************************************" 63 | Out-LogFile -string "END OUT-STATUSFILE" 64 | Out-LogFile -string "********************************************************************************" 65 | 66 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Information regarding the use, features, and code changes of the DLConversionV2 module can be found at 2 | 3 | https://timmcmic.wordpress.com 4 | 5 | Refer to the pinned blog post that is a table of contents. -------------------------------------------------------------------------------- /remove-groupViaGraph.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function removes the group from EntraID via Graph. 5 | 6 | .DESCRIPTION 7 | 8 | This function removes the group from EntraID via Graph. 9 | 10 | .PARAMETER groupObjectID 11 | 12 | The object ID of the group from EntraID 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | remove-groupViaGraph -groupObjectID $groupObjectID 21 | 22 | #> 23 | Function remove-groupViaGraph 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupObjectID 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | #Start function processing. 38 | 39 | Out-LogFile -string "********************************************************************************" 40 | Out-LogFile -string "BEGIN REMOVE-GROUPVIAGRAPH" 41 | Out-LogFile -string "********************************************************************************" 42 | 43 | try { 44 | Remove-MGGroup -groupID $groupObjectID -errorAction STOP 45 | } 46 | catch { 47 | out-logfile -string $_ 48 | out-logfile -string "Unable to remove group via graph" 49 | } 50 | 51 | Out-LogFile -string "END REMOVE-GROUPVIAGRAPH" 52 | Out-LogFile -string "********************************************************************************" 53 | } -------------------------------------------------------------------------------- /remove-o365CloudOnlyGroup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function disabled the on premies distribution list - removing it from azure ad and exchange online. 5 | 6 | .DESCRIPTION 7 | 8 | This function disabled the on premies distribution list - removing it from azure ad and exchange online. 9 | 10 | .PARAMETER parameterSet 11 | 12 | These are the parameters that will be manually cleared from the object in AD mode. 13 | 14 | .PARAMETER DN 15 | 16 | The DN of the group to remove. 17 | 18 | .PARAMETER GlobalCatalog 19 | 20 | The global catalog server the operation should be performed on. 21 | 22 | .PARAMETER UseExchange 23 | 24 | If set to true disablement will occur using the exchange on premises powershell commands. 25 | 26 | .OUTPUTS 27 | 28 | No return. 29 | 30 | .EXAMPLE 31 | 32 | Disable-OriginalDL -originalDLConfiguration $configuration -globalCatalogServer $GC -parameterSet $parameterArray -adCredential $cred 33 | 34 | #> 35 | Function remove-o365CloudOnlyGroup 36 | { 37 | [cmdletbinding()] 38 | 39 | Param 40 | ( 41 | [Parameter(Mandatory = $true)] 42 | $office365DLConfiguration, 43 | [Parameter(Mandatory = $false)] 44 | $DLCleanupRequired=$false 45 | ) 46 | 47 | #Output all parameters bound or unbound and their associated values. 48 | 49 | Out-LogFile -string "********************************************************************************" 50 | Out-LogFile -string "BEGIN remove-o365CloudOnlyGroup" 51 | Out-LogFile -string "********************************************************************************" 52 | 53 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 54 | 55 | if ($DLCleanupRequired -eq $FALSE) 56 | { 57 | try{ 58 | remove-o365DistributionGroup -identity $office365DLConfiguration.externalDirectoryObjectID -confirm:$FALSE -BypassSecurityGroupManagerCheck -errorAction STOP 59 | } 60 | catch{ 61 | out-logfile -string "Error removing the original distribution list from Office 365." 62 | out-logfile -string $_ -isError:$TRUE 63 | } 64 | } 65 | else 66 | { 67 | try{ 68 | remove-o365DistributionGroup -identity $office365DLConfiguration.externalDirectoryObjectID -confirm:$FALSE -BypassSecurityGroupManagerCheck -errorAction STOP 69 | } 70 | catch{ 71 | out-logfile -string "Error removing the original distribution list from Office 365 - not failing is optional cleanup operation." 72 | out-logfile -string $_ 73 | } 74 | } 75 | 76 | Out-LogFile -string "********************************************************************************" 77 | Out-LogFile -string "END remove-o365CloudOnlyGroup" 78 | Out-LogFile -string "********************************************************************************" 79 | } -------------------------------------------------------------------------------- /remove-onPremGroup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function disables all open powershell sessions. 5 | 6 | .DESCRIPTION 7 | 8 | This function disables all open powershell sessions. 9 | 10 | .PARAMETER globalCatalogServer 11 | 12 | The global catalog server to run operations on. 13 | 14 | .PARAMETER originalDLConfiguration 15 | 16 | The original DL configuration 17 | 18 | .PARAMETER adCredential 19 | 20 | The active directory credential 21 | 22 | .OUTPUTS 23 | 24 | No return. 25 | 26 | .EXAMPLE 27 | 28 | disable-allPowerShellSessions -globalCatalogServer $GC -originalDLConfiguration $config -adCredential $CRED 29 | 30 | #> 31 | Function remove-onPremGroup 32 | { 33 | [cmdletbinding()] 34 | 35 | Param 36 | ( 37 | [Parameter(Mandatory = $true)] 38 | [string]$globalCatalogServer, 39 | [Parameter(Mandatory = $true)] 40 | $originalDLConfiguration, 41 | [Parameter(Mandatory = $true)] 42 | $adCredential, 43 | [Parameter(Mandatory = $false)] 44 | [ValidateSet("Basic","Negotiate")] 45 | $activeDirectoryAuthenticationMethod="Negotiate" 46 | ) 47 | 48 | #Output all parameters bound or unbound and their associated values. 49 | 50 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 51 | 52 | [string]$isTestError="No" 53 | 54 | Out-LogFile -string "********************************************************************************" 55 | Out-LogFile -string "BEGIN remove-onPremGroup" 56 | Out-LogFile -string "********************************************************************************" 57 | 58 | out-logFile -string "Remove on premises distribution group." 59 | 60 | try 61 | { 62 | remove-adobject -identity $originalDLConfiguration.distinguishedName -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -confirm:$FALSE -errorAction STOP 63 | } 64 | catch 65 | { 66 | out-logfile -string $_ 67 | $isTestError="Yes" 68 | } 69 | 70 | Out-LogFile -string "END remove-onPremGroup" 71 | Out-LogFile -string "********************************************************************************" 72 | 73 | return $isTestError 74 | } -------------------------------------------------------------------------------- /send-TelemetryEvent.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function submits telemetry events to Azure. 5 | 6 | .DESCRIPTION 7 | 8 | This function submits telemetry events to Azure. 9 | 10 | .PARAMETER allowTelemetryCollection 11 | 12 | Boolean to allow for basic telemetry collection. 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | 21 | 22 | #> 23 | Function send-TelemetryEvent 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $TRUE)] 30 | [string]$traceModuleName, 31 | [Parameter(Mandatory = $TRUE)] 32 | $eventProperties, 33 | [Parameter(Mandatory = $TRUE)] 34 | $eventMetrics, 35 | [Parameter(Mandatory = $TRUE)] 36 | $eventName 37 | ) 38 | 39 | 40 | Send-THEvent -EventName $eventName -PropertiesHash $eventProperties -MetricsHash $eventMetrics -ModuleName $traceModuleName -Verbose 41 | } -------------------------------------------------------------------------------- /show-QuickStartGuide.ps1: -------------------------------------------------------------------------------- 1 | #This function opens the current quick start guide for the version of DL Conversion V2 loaded. 2 | 3 | function show-QuickStartGuide 4 | { 5 | #First - determine where the module is installed. 6 | 7 | $moduleName = "DLConversionV2" 8 | $ModulePSD1 = "DLConversionV2.psd1" 9 | $quickStartName = "QuickStartGuide.txt" 10 | 11 | write-host ("Module Name: "+$moduleName) 12 | 13 | $availableModules=@() 14 | 15 | $availableModules += get-module $moduleName -listAvailable | sort-object Version -Descending 16 | 17 | foreach ($module in $availableModules) 18 | { 19 | write-host ("Module Version: "+$module.version + " Module Path: "+$module.path ) 20 | } 21 | 22 | $modulePath = $availableModules[0].path 23 | 24 | write-host $modulePath 25 | 26 | $quickStartPath = $modulePath.replace($modulePSD1,$quickStartName) 27 | 28 | write-host $quickStartPath 29 | 30 | Invoke-Item $quickStartPath 31 | } -------------------------------------------------------------------------------- /show-SampleMigrationScript.ps1: -------------------------------------------------------------------------------- 1 | #This function opens the current quick start guide for the version of DL Conversion V2 loaded. 2 | 3 | function show-SampleMigrationScript 4 | { 5 | #First - determine where the module is installed. 6 | 7 | $moduleName = "DLConversionV2" 8 | $ModulePSD1 = "DLConversionV2.psd1" 9 | $quickStartName = "SampleMigrationScript.txt" 10 | 11 | write-host ("Module Name: "+$moduleName) 12 | 13 | $availableModules=@() 14 | 15 | $availableModules += get-module $moduleName -listAvailable | sort-object Version -Descending 16 | 17 | foreach ($module in $availableModules) 18 | { 19 | write-host ("Module Version: "+$module.version + " Module Path: "+$module.path ) 20 | } 21 | 22 | $modulePath = $availableModules[0].path 23 | 24 | write-host $modulePath 25 | 26 | $quickStartPath = $modulePath.replace($modulePSD1,$quickStartName) 27 | 28 | write-host $quickStartPath 29 | 30 | Invoke-Item $quickStartPath 31 | } -------------------------------------------------------------------------------- /start-ReplaceOnPrem.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function resets the on premises dependencies of the group that was mirgated. 5 | 6 | .DESCRIPTION 7 | 8 | This function resets the on premises dependencies of the group that was mirgated. 9 | 10 | .PARAMETER routingContact 11 | 12 | The original configuration of the DL on premises. 13 | 14 | .PARAMETER attributeOperation 15 | 16 | The attibute that we will be operating against. 17 | 18 | .PARAMETER canonicalObject 19 | 20 | The canonical object that will be reset. 21 | 22 | .PARAMETER adCredential 23 | 24 | The active directory credential 25 | 26 | .PARAMETER globalCatalogServer 27 | 28 | The global catalog server. 29 | 30 | .OUTPUTS 31 | 32 | None 33 | 34 | .EXAMPLE 35 | 36 | sstart-replaceONPrem -canonicalObject $object -attributeOperation $attribute -routingContactConfiguration $routingContactDN -adCredential $cred 37 | 38 | #> 39 | Function start-ReplaceOnPrem 40 | { 41 | [cmdletbinding()] 42 | 43 | Param 44 | ( 45 | [Parameter(Mandatory = $true)] 46 | $routingContact, 47 | [Parameter(Mandatory = $true)] 48 | [string]$attributeOperation, 49 | [Parameter(Mandatory = $true)] 50 | $canonicalObject, 51 | [Parameter(Mandatory = $true)] 52 | $adCredential, 53 | [Parameter(Mandatory = $true)] 54 | [string]$globalCatalogServer, 55 | [Parameter(Mandatory = $false)] 56 | [ValidateSet("Basic","Negotiate")] 57 | $activeDirectoryAuthenticationMethod="Negotiate" 58 | ) 59 | 60 | #Output all parameters bound or unbound and their associated values. 61 | 62 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 63 | 64 | [string]$isTestError="No" 65 | 66 | #Start function processing. 67 | 68 | Out-LogFile -string "********************************************************************************" 69 | Out-LogFile -string "BEGIN start-ReplaceOnPrem" 70 | Out-LogFile -string "********************************************************************************" 71 | 72 | #Declare function variables. 73 | 74 | 75 | $functionContactObject = get-canonicalName -globalCatalogServer $globalCatalogServer -dn $routingContact.distinguishedName -adCredential $adCredential -activeDirectoryAuthenticationMethod $activeDirectoryAuthenticationMethod 76 | $loopCounter=0 77 | $functionSleepTest=$FALSE 78 | $loopError=$FALSE 79 | 80 | 81 | out-Logfile -string "Processing operation..." 82 | 83 | #If the contact and the object to operate on are in the same domain - the utilize the same GC that we have for other operations. 84 | #If not - we'll need to utilize the domain name as the server - and allow the AD commandlts to make a best attempt against a DC in that domain based on "best selection." 85 | 86 | if ($functionContactObject.canonicalDomainName -eq $canonicalObject.canonicalDomainName) 87 | { 88 | out-logfile -string "Source and Target objects are in the same domain - utilize GC." 89 | 90 | try{ 91 | set-adobject -identity $canonicalObject.distinguishedName -add @{$attributeOperation=$routingContact.distinguishedName} -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 92 | } 93 | catch{ 94 | out-logfile -string $_ 95 | $isTestError="Yes" 96 | } 97 | } 98 | else 99 | { 100 | out-logfile -string "Source and target are in different domains - adding additional sleep and trying operation." 101 | 102 | do { 103 | $loopError = $FALSE 104 | 105 | if ($functionSleepTest -ne $FALSE) 106 | { 107 | start-sleepProgress -sleepString "Failed adding member to the group - sleeping before retry." -sleepSeconds 30 108 | 109 | } 110 | 111 | try 112 | { 113 | set-adobject -identity $canonicalObject.distinguishedName -add @{$attributeOperation=$routingContact.distinguishedName} -server $canonicalObject.canonicalDomainName -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 114 | 115 | $functionSleepTest=$TRUE 116 | 117 | $loopCounter++ 118 | } 119 | catch 120 | { 121 | out-logfile -string "Error adding member to group." 122 | 123 | $loopError = $TRUE 124 | } 125 | } while (($loopError -eq $TRUE) -and ($loopCounter -eq 10)) 126 | } 127 | 128 | if ($loopCounter -eq 10) 129 | { 130 | out-logfile -string "ERROR adding member to group." 131 | out-logfile -string $canonicalObject.canonicalName 132 | $isTestError="Yes" 133 | } 134 | else 135 | { 136 | out-logfile -string "Operation processed successfully" 137 | } 138 | 139 | 140 | Out-LogFile -string "END start-replaceOnPrem" 141 | Out-LogFile -string "********************************************************************************" 142 | 143 | return $isTestError 144 | } -------------------------------------------------------------------------------- /start-ReplaceOnPremSV.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function resets the on premises dependencies of the group that was mirgated. 5 | 6 | .DESCRIPTION 7 | 8 | This function resets the on premises dependencies of the group that was mirgated. 9 | 10 | .PARAMETER routingContact 11 | 12 | The routing contact configuration that will be utilized to replace membership. 13 | 14 | .PARAMETER attributeOperation 15 | 16 | The attibute that we will be operating against. 17 | 18 | .PARAMETER canonicalObject 19 | 20 | The canonical object that will be reset. 21 | 22 | .PARAMETER adCredential 23 | 24 | The active directory credential 25 | 26 | .PARAMETER globalCatalogServer 27 | 28 | The GC to replace member operations on. 29 | 30 | .OUTPUTS 31 | 32 | None 33 | 34 | .EXAMPLE 35 | 36 | sstart-replaceONPrem -canonicalObject $object -attributeOperation $attribute -routingContactConfiguration $routingContactDN -adCredential $cred 37 | 38 | #> 39 | Function start-ReplaceOnPremSV 40 | { 41 | [cmdletbinding()] 42 | 43 | Param 44 | ( 45 | [Parameter(Mandatory = $true)] 46 | $routingContact, 47 | [Parameter(Mandatory = $true)] 48 | [string]$attributeOperation, 49 | [Parameter(Mandatory = $true)] 50 | $canonicalObject, 51 | [Parameter(Mandatory = $true)] 52 | $adCredential, 53 | [Parameter(Mandatory = $true)] 54 | [string]$globalCatalogServer, 55 | [Parameter(Mandatory = $false)] 56 | [ValidateSet("Basic","Negotiate")] 57 | $activeDirectoryAuthenticationMethod="Negotiate" 58 | 59 | ) 60 | 61 | #Output all parameters bound or unbound and their associated values. 62 | 63 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 64 | 65 | #Start function processing. 66 | 67 | Out-LogFile -string "********************************************************************************" 68 | Out-LogFile -string "BEGIN start-ReplaceOnPremSV" 69 | Out-LogFile -string "********************************************************************************" 70 | 71 | #Declare function variables. 72 | 73 | 74 | $functionContactObject = get-canonicalName -globalCatalogServer $globalCatalogServer -dn $routingContact.distinguishedName -adCredential $adCredential -activeDirectoryAuthenticationMethod $activeDirectoryAuthenticationMethod 75 | $loopCounter=0 76 | $functionSleepTest=$FALSE 77 | $loopError=$FALSE 78 | 79 | 80 | out-Logfile -string "Processing operation..." 81 | 82 | #If the contact and the object to operate on are in the same domain - the utilize the same GC that we have for other operations. 83 | #If not - we'll need to utilize the domain name as the server - and allow the AD commandlts to make a best attempt against a DC in that domain based on "best selection." 84 | 85 | if ($functionContactObject.canonicalDomainName -eq $canonicalObject.canonicalDomainName) 86 | { 87 | out-logfile -string "Source and Target objects are in the same domain - utilize GC." 88 | 89 | try{ 90 | set-adobject -identity $canonicalObject.distinguishedName -replace @{$attributeOperation=$routingContact.distinguishedName} -server $globalCatalogServer -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 91 | } 92 | catch{ 93 | out-logfile -string $_ -isError:$TRUE 94 | } 95 | } 96 | else 97 | { 98 | out-logfile -string "Source and target are in different domains - adding additional sleep and trying operation." 99 | 100 | do { 101 | $loopError = $FALSE 102 | 103 | if ($functionSleepTest -ne $FALSE) 104 | { 105 | start-sleepProgress -sleepString "Failed adding member to the group." -sleepSeconds 30 106 | 107 | } 108 | 109 | try 110 | { 111 | set-adobject -identity $canonicalObject.distinguishedName -replace @{$attributeOperation=$routingContact.distinguishedName} -server $canonicalObject.canonicalDomainName -credential $adCredential -authType $activeDirectoryAuthenticationMethod -errorAction STOP 112 | 113 | $functionSleepTest=$TRUE 114 | 115 | $loopCounter++ 116 | } 117 | catch 118 | { 119 | out-logfile -string "Error adding member to group." 120 | 121 | $loopError = $TRUE 122 | } 123 | } while (($loopError -eq $TRUE) -and ($loopCounter -eq 10)) 124 | } 125 | 126 | if ($loopCounter -eq 10) 127 | { 128 | out-logfile -string "ERROR adding member to group." 129 | out-logfile -string $canonicalObject.canonicalName 130 | } 131 | else 132 | { 133 | out-logfile -string "Operation processed successfully" 134 | } 135 | 136 | 137 | Out-LogFile -string "END start-replaceOnPremSV" 138 | Out-LogFile -string "********************************************************************************" 139 | } -------------------------------------------------------------------------------- /start-replaceOffice365Dynamic.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function begins the process of replacing the Office 365 settings for dynamic groups that have been migrated that had cloud only dependencies. 5 | 6 | .DESCRIPTION 7 | 8 | This function begins the process of replacing the Office 365 settings for dynamic groups that have been migrated that had cloud only dependencies. 9 | 10 | .PARAMETER office365Attribute 11 | 12 | The office 365 attribute. 13 | 14 | .PARAMETER office365Member 15 | 16 | The member that is being added. 17 | 18 | .PARAMETER groupSMTPAddress 19 | 20 | The member that is being added. 21 | 22 | .OUTPUTS 23 | 24 | None 25 | 26 | .EXAMPLE 27 | 28 | sstart-ReplaceOffice365Dynamic -office365Attribute Attribute -office365Member groupMember -groupSMTPAddress smtpAddess 29 | 30 | #> 31 | Function start-ReplaceOffice365Dynamic 32 | { 33 | [cmdletbinding()] 34 | 35 | Param 36 | ( 37 | [Parameter(Mandatory = $true)] 38 | $office365Attribute, 39 | [Parameter(Mandatory = $true)] 40 | [string]$office365Member, 41 | [Parameter(Mandatory = $true)] 42 | [string]$groupSMTPAddress 43 | ) 44 | 45 | #Output all parameters bound or unbound and their associated values. 46 | 47 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 48 | 49 | [string]$isTestError="No" 50 | 51 | #Start function processing. 52 | 53 | Out-LogFile -string "********************************************************************************" 54 | Out-LogFile -string "BEGIN start-ReplaceOffice365Dynamic" 55 | Out-LogFile -string "********************************************************************************" 56 | 57 | #Log the parameters and variables for the function. 58 | 59 | $functionCommand=$NULL 60 | 61 | #Declare function variables. 62 | 63 | out-Logfile -string "Processing operation..." 64 | 65 | if ($office365Attribute -eq "ManagedBy") 66 | { 67 | out-logfile -string "Attribute is managedBy - this is single value on Dynamic DLs" 68 | 69 | $functionCommand="set-o365DynamicDistributionGroup -identity $office365Member -$office365Attribute '$groupSMTPAddress' -errorAction STOP" 70 | 71 | $scriptBlock = [scriptBlock]::create($functionCommand) 72 | 73 | out-logfile -string ("The command to execute: "+$functionCommand) 74 | 75 | try{ 76 | & $scriptBlock 77 | } 78 | catch{ 79 | out-logfile -string $_ 80 | $isTestError="Yes" 81 | } 82 | } 83 | else 84 | { 85 | $functionCommand="set-o365DynamicDistributionGroup -identity $office365Member -$office365Attribute @{add='$groupSMTPAddress'} -errorAction STOP" 86 | out-logfile -string ("The command to execute: "+$functionCommand) 87 | 88 | $scriptBlock = [scriptBlock]::create($functionCommand) 89 | 90 | out-logfile -string ("The script block to execute is: "+$scriptBlock) 91 | 92 | try { 93 | & $scriptBlock 94 | } 95 | catch { 96 | out-logfile -string $_ 97 | $isTestError="Yes" 98 | } 99 | } 100 | 101 | Out-LogFile -string "END start-ReplaceOffice365Dynamic" 102 | Out-LogFile -string "********************************************************************************" 103 | 104 | return $isTestError 105 | } -------------------------------------------------------------------------------- /start-replaceOffice365Members.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function updates the membership of any cloud only distribution lists for the migrated distribution group. 5 | 6 | .DESCRIPTION 7 | 8 | This function updates the membership of any cloud only distribution lists for the migrated distribution group. 9 | 10 | .PARAMETER office365Group 11 | 12 | The member that is being added. 13 | 14 | .PARAMETER groupSMTPAddress 15 | 16 | The member that is being added. 17 | 18 | .OUTPUTS 19 | 20 | None 21 | 22 | .EXAMPLE 23 | 24 | sstart-replaceOffice365Members -office365Group $group -groupSMTPAddress $address 25 | 26 | #> 27 | Function start-replaceOffice365Members 28 | { 29 | [cmdletbinding()] 30 | 31 | Param 32 | ( 33 | [Parameter(Mandatory = $true)] 34 | $office365Group, 35 | [Parameter(Mandatory = $true)] 36 | [string]$groupSMTPAddress 37 | ) 38 | 39 | #Output all parameters bound or unbound and their associated values. 40 | 41 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 42 | 43 | [string]$isTestError="No" 44 | 45 | #Start function processing. 46 | 47 | Out-LogFile -string "********************************************************************************" 48 | Out-LogFile -string "BEGIN start-ReplaceOffice365Members" 49 | Out-LogFile -string "********************************************************************************" 50 | 51 | #Log the parameters and variables for the function. 52 | 53 | $functionCommand=$NULL 54 | 55 | #Declare function variables. 56 | 57 | out-Logfile -string "Processing operation..." 58 | 59 | try{ 60 | add-o365DistributionGroupMember -identity $office365Group.primarySMTPAddress -member $groupSMTPAddress -errorAction STOP -BypassSecurityGroupManagerCheck 61 | } 62 | catch{ 63 | out-logfile -string $_ 64 | $isTestError="Yes" 65 | } 66 | 67 | 68 | Out-LogFile -string "END start-replaceOffice365Members" 69 | Out-LogFile -string "********************************************************************************" 70 | 71 | return $isTestError 72 | } -------------------------------------------------------------------------------- /start-replaceOffice365Unified.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function begins the process of replacing the Office 365 unified group settings for groups that have been migrated that had cloud only dependencies. 5 | 6 | .DESCRIPTION 7 | 8 | This function begins the process of replacing the Office 365 unified group settings for groups that have been migrated that had cloud only dependencies. 9 | 10 | .PARAMETER office365Attribute 11 | 12 | The office 365 attribute. 13 | 14 | .PARAMETER office365Member 15 | 16 | The member that is being added. 17 | 18 | .PARAMETER groupSMTPAddress 19 | 20 | The member that is being added. 21 | 22 | .OUTPUTS 23 | 24 | None 25 | 26 | .EXAMPLE 27 | 28 | sstart-replaceOffice365 -office365Attribute Attribute -office365Member groupMember -groupSMTPAddress smtpAddess 29 | 30 | #> 31 | Function start-replaceOffice365Unified 32 | { 33 | [cmdletbinding()] 34 | 35 | Param 36 | ( 37 | [Parameter(Mandatory = $true)] 38 | $office365Attribute, 39 | [Parameter(Mandatory = $true)] 40 | [string]$office365Member, 41 | [Parameter(Mandatory = $true)] 42 | [string]$groupSMTPAddress 43 | ) 44 | 45 | #Output all parameters bound or unbound and their associated values. 46 | 47 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 48 | 49 | [string]$isTestError="No" 50 | 51 | #Start function processing. 52 | 53 | Out-LogFile -string "********************************************************************************" 54 | Out-LogFile -string "BEGIN start-ReplaceOffice365Unified" 55 | Out-LogFile -string "********************************************************************************" 56 | 57 | #Log the parameters and variables for the function. 58 | 59 | $functionCommand=$NULL 60 | 61 | #Declare function variables. 62 | 63 | out-Logfile -string "Processing operation..." 64 | 65 | $functionCommand="set-o365UnifiedGroup -identity $office365Member -$office365Attribute @{add='$groupSMTPAddress'} -errorAction STOP" 66 | out-logfile -string ("The command to execute: "+$functionCommand) 67 | 68 | $scriptBlock = [scriptBlock]::create($functionCommand) 69 | 70 | out-logfile -string ("The script block to execute is: "+$scriptBlock) 71 | 72 | try { 73 | & $scriptBlock 74 | } 75 | catch { 76 | out-logfile -string $_ 77 | $isTestError="Yes" 78 | } 79 | 80 | Out-LogFile -string "END start-replaceOffice365Unified" 81 | Out-LogFile -string "********************************************************************************" 82 | 83 | return $isTestError 84 | } -------------------------------------------------------------------------------- /start-sleepProgress.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function performs a sleep operation with progress. 5 | 6 | .DESCRIPTION 7 | 8 | This function performs a sleep operation with progress. 9 | 10 | .PARAMETER sleepString 11 | 12 | String to display in the status window. 13 | 14 | .PARAMETER sleepSeconds 15 | 16 | Seconds to sleep. 17 | 18 | .PARAMETER sleepID 19 | 20 | The sleep id for text display. 21 | 22 | .PARAMETER sleepParentID 23 | 24 | The status message parent ID for sub progress status. 25 | 26 | .OUTPUTS 27 | 28 | No return. 29 | 30 | .EXAMPLE 31 | 32 | start-sleepProgess -sleepString "String" -seconds Seconds 33 | 34 | #> 35 | Function start-sleepProgress 36 | { 37 | [cmdletbinding()] 38 | 39 | Param 40 | ( 41 | [Parameter(Mandatory = $true)] 42 | [string]$sleepString, 43 | [Parameter(Mandatory = $true)] 44 | [int]$sleepSeconds, 45 | [Parameter(Mandatory = $false)] 46 | [int]$sleepParentID=0, 47 | [Parameter(Mandatory = $false)] 48 | [int]$sleepID=0 49 | ) 50 | 51 | #Output all parameters bound or unbound and their associated values. 52 | 53 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 54 | 55 | Out-LogFile -string "********************************************************************************" 56 | Out-LogFile -string "BEGIN start-sleepProgess" 57 | Out-LogFile -string "********************************************************************************" 58 | 59 | if(($sleepId -eq 0)-and ($sleepParentID -eq 0)) 60 | { 61 | For ($i=$sleepSeconds; $i -gt 0; $i--) 62 | { 63 | Write-Progress -Activity $sleepString -SecondsRemaining $i 64 | Start-Sleep 1 65 | } 66 | 67 | write-progress -activity $sleepString -Completed 68 | } 69 | else 70 | { 71 | For ($i=$sleepSeconds; $i -gt 0; $i--) 72 | { 73 | Write-Progress -Activity $sleepString -SecondsRemaining $i -Id $sleepID -ParentId $sleepParentID 74 | Start-Sleep 1 75 | } 76 | 77 | Write-Progress -Activity $sleepString -Id $sleepID -ParentId $sleepParentID -Completed 78 | } 79 | 80 | Out-LogFile -string "END start-sleepProgess" 81 | Out-LogFile -string "********************************************************************************" 82 | } -------------------------------------------------------------------------------- /start-telemetryConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function prepares telemetry configuration data submission to Azure App Insights 5 | 6 | .DESCRIPTION 7 | 8 | This function prepares telemetry configuration data submission to Azure App Insights. 9 | 10 | .PARAMETER allowTelemetryCollection 11 | 12 | Boolean to allow for basic telemetry collection. 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | start-telemetryConfiguration -allowTelemetryConfiguration $TRUE 21 | 22 | #> 23 | Function start-telemetryConfiguration 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [boolean]$allowTelemetryCollection, 31 | [Parameter(Mandatory = $TRUE)] 32 | [string]$appInsightAPIKey, 33 | [Parameter(Mandatory = $TRUE)] 34 | [string]$traceModuleName 35 | ) 36 | 37 | $functionInstrumentationKey = $traceModuleName+".ApplicationInsights.InstrumentationKey" 38 | $functionConnectionStringKey = $traceModuleName+".ApplicationInsights.ConnectionString" 39 | $functionOptIn = $traceModuleName+".OptIn" 40 | $functionIgnoreGDPR = $traceModuleName+".IgnoreGDPR" 41 | $functionRemovePII = $traceModuleName+".RemovePII" 42 | $functionModuleName = "TelemetryHelper" 43 | [string]$functionConnectionString = "InstrumentationKey=63d673af-33f4-401c-931e-f0b64a218d89;IngestionEndpoint=https://eastus2-3.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/" 44 | 45 | Set-PSFConfig -Module $functionModuleName -Name $functionInstrumentationKey -Value $appInsightAPIKey -Initialize -Validation string -Description 'Your ApplicationInsights instrumentation key' -Hidden 46 | Set-PSFConfig -Module $functionModuleName -Name $functionConnectionStringKey -value $functionConnectionString -Initialize -validation string -Description "Connection string for api workspace." -Hidden 47 | #Set-PSFConfig -Module 'TelemetryHelper' -Name 'TelemetryHelper.OptInVariable' -Value 'TelemetryHelperTelemetryOptIn' -Initialize -Validation string -Description 'The name of the environment variable used to indicate that telemetry should be sent' 48 | Set-PSFConfig -Module $functionModuleName -Name $functionOptIn -Value $allowTelemetryCollection -Initialize -Validation bool -Description 'Whether user opts into telemetry or not' 49 | Set-PSFConfig -Module $functionModuleName -Name $functionIgnoreGDPR -Value $false -Initialize -Validation bool -Description 'Whether telemetry client should ignore user settings, e.g. if you are not bound by GDPR or other regulations' 50 | Set-PSFConfig -Module $functionModuleName -Name $functionRemovePII -VAlue $true -Initialize -Validation bool -Description "Whether information like the computer name should be stripped from the data that is sent" 51 | } -------------------------------------------------------------------------------- /start-upgradeToOffice365Group.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function triggers the upgrade of the group to an Office 365 Modern / Unified Group 5 | 6 | .DESCRIPTION 7 | 8 | This function triggers the upgrade of the group to an Office 365 Modern / Unified Group 9 | 10 | .PARAMETER groupSMTPAddress 11 | 12 | THe SMTP address of the group to trigger the upgrade on. 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | start-upgradeToOffice365Group -groupSMTPAddress address 21 | 22 | #> 23 | Function start-upgradeToOffice365Group 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupSMTPAddress 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | [string]$isTestError="No" 38 | 39 | #Declare function variables. 40 | 41 | #Start function processing. 42 | 43 | Out-LogFile -string "********************************************************************************" 44 | Out-LogFile -string "BEGIN start-upgradeToOffice365Group" 45 | Out-LogFile -string "********************************************************************************" 46 | 47 | #Call the command to begin the upgrade process. 48 | 49 | out-logFile -string "Calling command to being the upgrade process." 50 | out-logfile -string "NOTE: This command runs in the background and no status is provided." 51 | out-logfile -string "Administrators MUST validate the upgrade as successful manually." 52 | 53 | try{ 54 | $attempt=upgrade-o365DistributionGroup -DlIdentities $groupSMTPAddress 55 | } 56 | catch{ 57 | out-logFile -string $_ 58 | $isTestError="Yes" 59 | } 60 | 61 | out-logfile -string $attempt 62 | out-logfile -string ("Upgrade attempt successfully submitted = "+$attempt.SuccessfullySubmittedForUpgrade) 63 | 64 | if ($attempt.reason -ne $NULL) 65 | { 66 | out-logfile -string ("Error Reason = "+$attempt.errorReason) 67 | $isTestError="Yes" 68 | } 69 | 70 | Out-LogFile -string "END start-upgradeToOffice365Group" 71 | Out-LogFile -string "********************************************************************************" 72 | 73 | return $isTestError 74 | } -------------------------------------------------------------------------------- /test-CloudDLPresent.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function loops until we detect that the cloud DL is no longer present. 5 | 6 | .DESCRIPTION 7 | 8 | This function loops until we detect that the cloud DL is no longer present. 9 | 10 | .PARAMETER groupSMTPAddress 11 | 12 | The SMTP Address of the group. 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | test-CloudDLPresent -groupSMTPAddress SMTPAddress 21 | 22 | #> 23 | Function test-CloudDLPresent 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | [string]$groupSMTPAddress, 31 | [Parameter(Mandatory = $FALSE)] 32 | $aadConnectPowershellSessionName=$NULL 33 | ) 34 | 35 | #Output all parameters bound or unbound and their associated values. 36 | 37 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 38 | 39 | #Declare function variables. 40 | 41 | [boolean]$firstLoopProcessing=$TRUE 42 | [int]$waitTime=0 43 | [int]$maxWaitTIme = 11 44 | 45 | #Start function processing. 46 | 47 | Out-LogFile -string "********************************************************************************" 48 | Out-LogFile -string "BEGIN TEST-CLOUDDLPRESENT" 49 | Out-LogFile -string "********************************************************************************" 50 | 51 | do 52 | { 53 | if ($firstLoopProcessing -eq $TRUE) 54 | { 55 | Out-LogFile -string "First time checking for group - do not sleep." 56 | $firstLoopProcessing = $FALSE 57 | } 58 | else 59 | { 60 | out-logfile -string "Determine if AD Connect should be proactively triggered (suspect thread 1 failure)." 61 | 62 | if (($waitTime -eq $maxWaitTime) -and ($aadConnectPowershellSessionName -ne $NULL)) 63 | { 64 | out-logfile -string "Time elapsed 5 minutes - proactively invoking AD Connect - assuming thread 1 failure in multi-migration." 65 | out-logfile -string "Start random sleep to ensure that no two threads try this at the same time - its possible." 66 | 67 | start-sleepProgress -sleepSeconds (get-random -Minimum 5 -Maximum 60) -sleepString "Sleeping before invoking AD connect suspected thread 1 failure." 68 | 69 | invoke-adconnect -PowershellSessionName $aadConnectPowershellSessionName -isSingleAttempt $TRUE -errorAction Continue 70 | 71 | $waitTime = 0 72 | } 73 | elseif ($aadConnectPowershellSessionName -eq $NULL) 74 | { 75 | out-logfile -string "Not a multithreaded migration - no proactive AD Connect calls." 76 | } 77 | else 78 | { 79 | out-logfile -string "No need to invoke ADConnect at this time." 80 | $waitTime++ 81 | } 82 | 83 | start-sleepProgress -sleepString "Group found in Office 365 - sleep for 30 seconds - try again." -sleepSeconds 30 84 | } 85 | 86 | } while (get-o365Recipient -identity $groupSMTPAddress) 87 | 88 | Out-LogFile -string "END TEST-CLOUDDLPRESENT" 89 | Out-LogFile -string "********************************************************************************" 90 | } -------------------------------------------------------------------------------- /test-credentials.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function validates the parameters within the script. Paramter validation is shared across functions. 5 | 6 | .DESCRIPTION 7 | 8 | This function validates the parameters within the script. Paramter validation is shared across functions. 9 | 10 | #> 11 | Function test-credentials 12 | { 13 | [cmdletbinding()] 14 | 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true)] 18 | $credentialsToTest 19 | ) 20 | 21 | #Output all parameters bound or unbound and their associated values. 22 | 23 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 24 | 25 | #Start function processing. 26 | 27 | Out-LogFile -string "********************************************************************************" 28 | Out-LogFile -string "BEGIN start-testCredentials" 29 | Out-LogFile -string "********************************************************************************" 30 | 31 | foreach ($credential in $credentialsToTest) 32 | { 33 | if ($credential.gettype().name -eq "PSCredential") 34 | { 35 | out-logfile -string ("Tested credential: "+$credential.userName) 36 | } 37 | else 38 | { 39 | out-logfile -string "Credential is not a valid PSCredential. All credentials must be PSCredential types." -isError:$TRUE 40 | } 41 | } 42 | 43 | Out-LogFile -string "********************************************************************************" 44 | Out-LogFile -string "END test-Credentials" 45 | Out-LogFile -string "********************************************************************************" 46 | } -------------------------------------------------------------------------------- /test-dlNameLength.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function checks the prefix and suffix to ensure that character limit constraints are not exceeded. 5 | 6 | .DESCRIPTION 7 | 8 | This function checks the prefix and suffix to ensure that character limit constraints are not exceeded. 9 | 10 | .PARAMETER DLConfiguration 11 | 12 | The DL configuration from active directory. 13 | 14 | .PARAMETER Prefix 15 | 16 | The DL name prefix. 17 | 18 | .PARAMETER Suffix 19 | 20 | The DL name suffix. 21 | 22 | .OUTPUTS 23 | 24 | None 25 | 26 | .EXAMPLE 27 | 28 | test-CloudDLPresent -groupSMTPAddress SMTPAddress 29 | 30 | #> 31 | Function test-DLNameLength 32 | { 33 | [cmdletbinding()] 34 | 35 | Param 36 | ( 37 | [Parameter(Mandatory = $true)] 38 | $dlConfiguration, 39 | [Parameter(Mandatory = $true)] 40 | [AllowEmptyString()] 41 | [string]$prefix, 42 | [Parameter(Mandatory = $true)] 43 | [AllowEmptyString()] 44 | [string]$suffix 45 | ) 46 | 47 | #Output all parameters bound or unbound and their associated values. 48 | 49 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 50 | 51 | #Declare function variables. 52 | 53 | [int]$functionMaxNameLength = 64 54 | [string]$functionTestString = "" 55 | 56 | #Start function processing. 57 | 58 | Out-LogFile -string "********************************************************************************" 59 | Out-LogFile -string "BEGIN TEST-dlNameLength" 60 | Out-LogFile -string "********************************************************************************" 61 | 62 | out-logfile -string ("Testing the DLName: "+$DLConfiguration.name) 63 | 64 | $functionTestString = $prefix + $dlConfiguration.Name 65 | 66 | out-logfile -string ("String with prefix: "+$functionTestString) 67 | 68 | $functionTestString = $functionTestString + $suffix 69 | 70 | out-logfile -string ("String with suffix: "+$functionTestString) 71 | 72 | if ($functionTestString.length -gt $functionMaxNameLength) 73 | { 74 | out-logfile -string "The max character length of 64 is exceeded." 75 | out-logfile -string "Record and error and fail." 76 | 77 | $functionObject = New-Object PSObject -Property @{ 78 | Alias = "" 79 | Name = $dlConfiguration.Name 80 | PrimarySMTPAddressOrUPN = "" 81 | GUID = "" 82 | RecipientType = "" 83 | ExchangeRecipientTypeDetails = "" 84 | ExchangeRecipientDisplayType = "" 85 | ExchangeRemoteRecipientType = "" 86 | GroupType = "" 87 | RecipientOrUser = "" 88 | ExternalDirectoryObjectID = "" 89 | OnPremADAttribute = "" 90 | DN = "" 91 | ParentGroupSMTPAddress = "" 92 | isAlreadyMigrated = $false 93 | isError=$TRUE 94 | isErrorMessage="NAME_LENGTH_EXCEPTION: The DL Name plus the prefix and / or suffix exceeds 64 characters. To complete migration wih the prefix and / or suffix the group name must be shortened to prefix + name + suffix to less than 64 characters." 95 | } 96 | 97 | $global:preCreateErrors+=$functionObject 98 | } 99 | else 100 | { 101 | out-logfile -string "Name with prefix and suffix is less than 64 characters - proceed with migration." 102 | } 103 | 104 | 105 | Out-LogFile -string "END TEST-dlNameLength" 106 | Out-LogFile -string "********************************************************************************" 107 | } -------------------------------------------------------------------------------- /test-itemCount.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function validates the parameters within the script. Paramter validation is shared across functions. 5 | 6 | .DESCRIPTION 7 | 8 | This function validates the parameters within the script. Paramter validation is shared across functions. 9 | 10 | #> 11 | Function test-itemCount 12 | { 13 | [cmdletbinding()] 14 | 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true)] 18 | $itemsToCount, 19 | [Parameter(Mandatory = $true)] 20 | $itemsToCompareCount 21 | ) 22 | 23 | #Output all parameters bound or unbound and their associated values. 24 | 25 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 26 | 27 | #Start function processing. 28 | 29 | Out-LogFile -string "********************************************************************************" 30 | Out-LogFile -string "BEGIN test-itemCount" 31 | Out-LogFile -string "********************************************************************************" 32 | 33 | 34 | if ($itemsToCount.count -lt $itemsToCompareCount.count) 35 | { 36 | out-logfile -string "ERROR: Credentials arrays must have one credential for each server specified." -isError:$TRUE 37 | } 38 | else 39 | { 40 | out-logfile -string "The number of credentials in the credentials array matches the number of servers provided." 41 | } 42 | 43 | Out-LogFile -string "********************************************************************************" 44 | Out-LogFile -string "END test-ItemCount" 45 | Out-LogFile -string "********************************************************************************" 46 | } -------------------------------------------------------------------------------- /test-nonSyncDL.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function loops until we detect that the cloud DL is no longer present. 5 | 6 | .DESCRIPTION 7 | 8 | This function loops until we detect that the cloud DL is no longer present. 9 | 10 | .PARAMETER originalDLConfiguration 11 | 12 | The SMTP Address of the group. 13 | 14 | .OUTPUTS 15 | 16 | None 17 | 18 | .EXAMPLE 19 | 20 | test-CloudDLPresent -originalDLConfiguration $config 21 | 22 | #> 23 | Function test-nonSyncDL 24 | { 25 | [cmdletbinding()] 26 | 27 | Param 28 | ( 29 | [Parameter(Mandatory = $true)] 30 | $originalDLConfiguration 31 | ) 32 | 33 | #Output all parameters bound or unbound and their associated values. 34 | 35 | write-functionParameters -keyArray $MyInvocation.MyCommand.Parameters.Keys -parameterArray $PSBoundParameters -variableArray (Get-Variable -Scope Local -ErrorAction Ignore) 36 | 37 | [array]$functionErrors=@() 38 | 39 | 40 | #Start function processing. 41 | 42 | Out-LogFile -string "********************************************************************************" 43 | Out-LogFile -string "BEGIN TEST-NONSYNCDL" 44 | Out-LogFile -string "********************************************************************************" 45 | 46 | out-logfile -string "Testing mail attribute..." 47 | 48 | if ($originalDLConfiguration.mail -eq $NULL) 49 | { 50 | $isErrorObject = new-Object psObject -property @{ 51 | Attribute = "Mail" 52 | ErrorMessage = ("Mail attribute missing on non-syncDL and is required.") 53 | ErrorMessageDetail = $_ 54 | } 55 | 56 | $functionErrors+=$isErrorObject 57 | } 58 | else 59 | { 60 | out-logfile -string "Attribute mail is present." 61 | } 62 | 63 | out-logfile -string "Testing legacyExchangeDN attribute..." 64 | 65 | if ($originalDLCOnfiguration.legacyExchangeDN -eq $NULL) 66 | { 67 | $isErrorObject = new-Object psObject -property @{ 68 | Attribute = "LegacyExchangeDN" 69 | ErrorMessage = ("LegacyExchangeDN attribute missing on non-syncDL and is required.") 70 | errorMessageDetail = $_ 71 | } 72 | 73 | $functionErrors+=$isErrorObject 74 | } 75 | else 76 | { 77 | out-logfile -string "Attribute legacyExchangeDN is present." 78 | } 79 | 80 | out-logfile -string "Testing proxyAddresses attribute..." 81 | 82 | if ($originalDLCOnfiguration.proxyAddresses -eq $NULL) 83 | { 84 | $isErrorObject = new-Object psObject -property @{ 85 | Attribute = "ProxyAddresses" 86 | ErrorMessage = ("ProxyAddresses attribute missing on non-syncDL and is required.") 87 | ErrorMessageDetail = $_ 88 | } 89 | 90 | $functionErrors+=$isErrorObject 91 | } 92 | else 93 | { 94 | out-logfile -string "Attribute proxyAddresses is present." 95 | } 96 | 97 | out-logfile -string "Testing mailNickName attribute..." 98 | 99 | if ($originalDLCOnfiguration.mailNickName -eq $NULL) 100 | { 101 | $isErrorObject = new-Object psObject -property @{ 102 | Attribute = "MailNickName" 103 | ErrorMessage = ("MailNickName attribute missing on non-syncDL and is required.") 104 | ErrorMessageDetail = $_ 105 | } 106 | 107 | $functionErrors+=$isErrorObject 108 | } 109 | else 110 | { 111 | out-logfile -string "Attribute mailNickName is present." 112 | } 113 | 114 | if ($functionErrors.count -gt 0) 115 | { 116 | foreach ($error in $functionErrors) 117 | { 118 | out-logfile -string "Error detected in non sync DL." 119 | out-logfile -string $error.attribute 120 | out-logfile -string $error.errormessage 121 | } 122 | 123 | out-logfile -string "All errors must be corrected prior to non-sync DL migration." -isError:$TRUE 124 | } 125 | else 126 | { 127 | out-logfile -string "No attribute validation errors found proceed with migration." 128 | } 129 | 130 | Out-LogFile -string "END TEST-NONSYNCDL" 131 | Out-LogFile -string "********************************************************************************" 132 | } -------------------------------------------------------------------------------- /write-FunctionParameters.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function outputs all of the parameters from a function to the log file for review. 5 | 6 | .DESCRIPTION 7 | 8 | This function outputs all of the parameters from a function to the log file for review. 9 | 10 | #> 11 | Function write-FunctionParameters 12 | { 13 | [cmdletbinding()] 14 | 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true)] 18 | $keyArray, 19 | [Parameter(Mandatory = $true)] 20 | $parameterArray, 21 | [Parameter(Mandatory = $true)] 22 | $variableArray 23 | ) 24 | 25 | Out-LogFile -string "********************************************************************************" 26 | 27 | $parameteroutput = @() 28 | 29 | foreach ($paramName in $keyArray) 30 | { 31 | $bound = $parameterArray.ContainsKey($paramName) 32 | 33 | $parameterObject = New-Object PSObject -Property @{ 34 | ParameterName = $paramName 35 | ParameterValue = if ($bound) { $parameterArray[$paramName] } 36 | else { ($variableArray | where {$_.name -eq $paramName } ).value } 37 | Bound = $bound 38 | } 39 | 40 | $parameterOutput+=$parameterObject 41 | } 42 | 43 | out-logfile -string $parameterOutput 44 | 45 | Out-LogFile -string "********************************************************************************" 46 | } -------------------------------------------------------------------------------- /write-errorEntry.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function writes the error entry to the log file. 5 | 6 | .DESCRIPTION 7 | 8 | This function writes the error entry to the log file. 9 | 10 | #> 11 | Function write-errorEntry 12 | { 13 | [cmdletbinding()] 14 | 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true)] 18 | $errorEntry 19 | ) 20 | 21 | out-logfile -string "=====" 22 | out-logfile -string ("Alias: "+$errorEntry.alias) 23 | out-logfile -string ("Name: "+$errorEntry.name) 24 | out-logfile -string ("PrimarySMTPAddressOrUPN: "+$errorEntry.primarySMTPAddressOrUPN) 25 | out-logfile -string ("RecipientType: "+$errorEntry.RecipientType) 26 | out-logfile -string ("GroupType: "+$errorEntry.GroupType) 27 | out-logfile -string ("RecipientOrUser: "+$errorEntry.recipientoruser) 28 | out-logfile -string ("ExternalDirectoryObjectID:" +$errorEntry.externalDirectoryObjectID) 29 | out-logfile -string ("OnPremADAttribute: "+$errorEntry.onPremADAttribute) 30 | out-logfile -string ("DN: "+$errorEntry.DN) 31 | out-logfile -string ("ParentGroupSMTPAddress: "+$errorEntry.parentGroupSMTPAddress) 32 | out-logfile -string ("isAlreadyMigrated: "+$errorEntry.isAlreadyMigrated) 33 | out-logfile -string ("isError: "+$errorEntry.isError) 34 | out-logfile -string ("isErrorMessage: "+$errorEntry.isErrorMessage) 35 | out-logfile -string "=====" 36 | } -------------------------------------------------------------------------------- /write-hashtable.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | 4 | This function writes the hash table to the log file. 5 | 6 | .DESCRIPTION 7 | 8 | This function writes the hash table to the log file. 9 | 10 | #> 11 | Function write-hashTable 12 | { 13 | [cmdletbinding()] 14 | 15 | Param 16 | ( 17 | [Parameter(Mandatory = $true)] 18 | $hashTable 19 | ) 20 | 21 | Out-LogFile -string "********************************************************************************" 22 | 23 | foreach ($key in $hashtable.GetEnumerator()) 24 | { 25 | out-logfile -string ("Key: "+$key.name+" is "+$key.Value.Description+" with value "+$key.Value.Value) 26 | } 27 | 28 | Out-LogFile -string "********************************************************************************" 29 | } -------------------------------------------------------------------------------- /write-shamelessPlug.ps1: -------------------------------------------------------------------------------- 1 | function write-shamelessPlug 2 | { 3 | out-logfile -string "*************************************************************" 4 | out-logfile -string "Shameless plug...this script is developed in my spare time..." 5 | out-logfile -string "If you are enjoying it - and it is working - consider letting my management know at dlconversionv2@service.microsoft.com" 6 | out-logfile -string "*************************************************************" 7 | } --------------------------------------------------------------------------------