Public/FlyProjectApi.ps1

<#
.SYNOPSIS
 
Create a new migration project
 
.DESCRIPTION
 
Create a new migration project
 
.PARAMETER Name
Specify the name of project
 
.PARAMETER SourceConnection
Specify the name of source connection
 
.PARAMETER DestinationConnection
Specify the name of destination connection
 
.PARAMETER Policy
Specify the name of policy which applied to the project
 
.PARAMETER Tags
Specify a list of tags to the project
 
.OUTPUTS
 
ProjectModel<PSCustomObject>
#>

function New-FlyMigrationProject {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [String]
        ${Name},
        [Parameter(Mandatory = $true)]
        [String]
        ${SourceConnection},
        [Parameter(Mandatory = $true)]
        [String]
        ${DestinationConnection},
        [Parameter(Mandatory = $true)]
        [String]
        ${Policy},
        [Parameter(Mandatory = $false)]
        [String[]]
        ${Tags}
    )

    Process {
        'Calling method: New-FlyMigrationProject' | Write-Debug
        $PSBoundParameters | Out-DebugParameter | Write-Debug
        Try {
            Resolve-FlyProjectName -ProjectName $Name
            $source = Get-FlyConnectionByName -ConnectionName $SourceConnection
            $destination = Get-FlyConnectionByName -ConnectionName $DestinationConnection
            $migrationModuleType = $source.ConnectionType
            if ($source.ConnectionType -eq [PlatformType]::SharePoint -and $destination.ConnectionType -eq [PlatformType]::GoogleDrive) {
                $migrationModuleType = 21
            }
            elseif ($source.ConnectionType -eq [PlatformType]::OneDrive -and $destination.ConnectionType -eq [PlatformType]::GoogleDrive) {
                $migrationModuleType = 22
            }
            $targetPolicy = Get-FlyPolicyByName -PolicyName $Policy -PlatformType $migrationModuleType
            #Construct the project creation model
            $projectModel = [PSCustomObject]@{
                "name"                    = $Name
                "sourcePlatform"          = $source.ConnectionType
                "sourceConnectionId"      = $source.Id
                "destinationPlatform"     = $destination.ConnectionType
                "destinationConnectionId" = $destination.id
                "policyId"                = $targetPolicy.Id
                "tagIds"                  = @()
            }
            if ($Tags -and $Tags.Count -gt 0) {
                $tagIds = $Tags | ForEach-Object { Get-FlyTagByName $PSItem } | Select-Object -Property Id | ForEach-Object { "$($_.Id)" }
                $projectModel.tagIds = @($tagIds)
            }
            #Create a new project
            $result = New-FlyProject -ProjectCreationModel $projectModel
            if ($result) {
                Write-Host 'Successfully created the project.' -ForegroundColor Green
                $result.sourcePlatform = [PlatformType].GetEnumName($result.sourcePlatform)
                $result.destinationPlatform = [PlatformType].GetEnumName($result.destinationPlatform)
                $result.createTime = [DateTime]$result.createTime
                $result.lastModifyTime = [DateTime]$result.lastModifyTime
            }
            return $result
        }
        Catch {
            Write-Host 'Failed to create the project.' -ForegroundColor Red
            ErrorDetail -Error $_
            throw
        }
    }
}

<#
.SYNOPSIS
 
Get the project by name
 
.DESCRIPTION
 
Get the project by name
 
.PARAMETER Name
The name of the project
 
.PARAMETER Platform
The platform of the project
 
.OUTPUTS
 
ProjectSummaryModel, the object model of the project
#>

function Get-FlyMigrationProject {
    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [String]
        ${Name}
    )

    Process {
        'Calling method: Get-FlyMigrationProject' | Write-Debug
        Try {
            $project = Get-FlyProjectByName -ProjectName $Name
            $project.sourcePlatform = [PlatformType].GetEnumName($project.sourcePlatform)
            $project.destinationPlatform = [PlatformType].GetEnumName($project.destinationPlatform)
            $project.createTime = [DateTime]$project.createTime
            $project.lastModifyTime = [DateTime]$project.lastModifyTime
            return $project
        }
        Catch {
            $_.Exception | Write-Debug
        }
        return $null
    }
}

<#
.SYNOPSIS
 
Import migration projects from csv file.
 
.DESCRIPTION
 
Import migration projects from csv file.
 
.PARAMETER Path
Specify the csv file path of the projects to import.
 
.PARAMETER OutFile
Specify the csv file path of the import report. If you do not specify the report path,the report file will be generated in the same directory as the import file.
 
.OUTPUTS
 
None
#>


function Import-FlyMigrationProjects {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [String]
        ${Path},
        [Parameter(Mandatory = $false)]
        [String]
        ${OutFile}
    ) 
    Process {
        'Calling method: Import-FlyMigrationProjects' | Write-Debug
        $PSBoundParameters | Out-DebugParameter | Write-Debug
        $importProjects = Get-FlyMappingsFromCsv -Path $Path
        if ($OutFile) {
            $isCsv = Confirm-FileExtension -Path $OutFile -AllowedExtensions 'csv'
            if (-not $isCsv) {
                throw 'The report file is not a CSV file.'
            }
        }
        $connectionCache = @{}
        $policyCache = @{}
        $tagCache = @{}
        $reports = @()
        foreach ($project in $importProjects) {
            $obj = [PSCustomObject]@{
                'Project name'           = $project.'Project name'
                'Source connection'      = $project.'Source connection'
                'Destination connection' = $project.'Destination connection'
                'Migration policy'       = $project.'Migration policy'
                'Tags'                   = $project.'Tags'
                'Status'                 = ''
                'Comment'                = ''
            }
            while ($true) {
                Try {
                    $validateProperties = @('Project name', 'Source connection', 'Destination connection', 'Migration policy')
                    Confirm-PSCustomObjectProperties -Object $obj -Properties $validateProperties
                    $srcConnection = $connectionCache[$obj.'Source connection']
                    $destConnection = $connectionCache[$obj.'Destination connection']
                    $targetPolicy = $policyCache[$obj.'Migration policy']
                    $tagIds = @()
                    if ($null -eq $srcConnection) {
                        $srcConnection = Get-FlyConnectionByName -ConnectionName $obj.'Source connection'
                        $connectionCache[$obj.'Source connection'] = $srcConnection
                    }
                    if ($null -eq $destConnection) {
                        $destConnection = Get-FlyConnectionByName -ConnectionName $obj.'Destination connection'
                        $connectionCache[$obj.'Destination connection'] = $destConnection
                    } 
                    if ($null -eq $targetPolicy) {
                        $targetPolicy = Get-FlyPolicyByName -PolicyName $obj.'Migration policy' -PlatformType $srcConnection.ConnectionType
                        $policyCache[$obj.'Migration policy'] = $targetPolicy
                    }
                    if ($obj.Tags) {
                        $tagNames = $obj.Tags.Split(';')
                        foreach ($tagName in $tagNames) {
                            $tagId = $tagCache[$tagName]
                            if ($null -eq $tagId) {
                                $tagId = (Get-FlyTagByName -TagName $tagName).Id
                                $tagCache[$tagName] = $tagId
                            }
                            if ($tagId) {
                                $tagIds += $tagId
                            }
                        }
                    }
                    #Construct the project creation model
                    $projectModel = [PSCustomObject]@{
                        "name"                    = $obj.'Project name'
                        "sourcePlatform"          = $srcConnection.ConnectionType
                        "sourceConnectionId"      = $srcConnection.Id
                        "destinationPlatform"     = $destConnection.ConnectionType
                        "destinationConnectionId" = $destConnection.id
                        "policyId"                = $targetPolicy.Id
                        "tagIds"                  = $tagIds
                    }
                    #Create a new project
                    $result = New-FlyProject -ProjectCreationModel $projectModel
                    if ($result) {
                        Write-Host ('Successfully created the project: {0}' -f $obj.'Project name')   -ForegroundColor Green
                        $obj.Status = 'Successful'
                        $reports += $obj
                    }
                    break
                }
                Catch {
                    if ($null -ne $_.ErrorDetails) {
                        Write-Host ('Failed to create the project: {0}. Details: {1}' -f $obj.'Project name', $_.ErrorDetails.Message) -ForegroundColor Red
                        Try {
                            $errorMessage = ConvertFrom-Json $_.ErrorDetails.Message
                            if ($errorMessage.statusCode -eq 429) {
                                $obj.Status = ''
                                $obj.Comment = ''
                                Write-Host ('Will try to create the project: {0} again after 60 seconds.' -f $obj.'Project name')
                                Start-Sleep -Seconds 60
                            }
                            else {
                                $obj.Status = 'Failed'
                                $obj.Comment = $errorMessage.errorMessage
                                $reports += $obj
                                break
                            }
                        }
                        Catch {
                            break
                        }
                    }
                    elseIf ($null -ne $_.Exception) {
                        Write-Host ('Failed to create the project: {0}. Details: {1}' -f $obj.'Project name', $_.Exception) -ForegroundColor Red
                        if ($null -ne $_.Exception.Response -and $_.Exception.Response.StatusCode -eq 429) {
                            $obj.Status = ''
                            $obj.Comment = ''
                            Write-Host ('Will try to create the project: {0} again after 60 seconds.' -f $obj.'Project name')
                            Start-Sleep -Seconds 60
                        }
                        else {
                            $obj.Status = 'Failed'
                            $obj.Comment = $_.Exception.Message
                            $reports += $obj
                            break
                        }
                    }
                }
            }
        }
        if (-not $OutFile) {
            $file = -Join ('Import_Projects_Report_', (Get-Date -Format "yyyyMMddHHmmss"), '.csv')
            $folder = Split-Path -Path $Path
            $OutFile = Join-Path $folder -ChildPath $file
        }
        $reports | Export-Csv -Path $OutFile -NoTypeInformation
        Write-Host ('Report path: {0}' -f $OutFile)
    }
}

<#
.SYNOPSIS
 
Restructure migration projects from csv file.
 
.DESCRIPTION
 
Restructure migration projects from csv file.
 
.PARAMETER Path
Specify the csv file path of the project mappings.
 
.PARAMETER OutFile
Specify the csv file path of the restructure result. If you do not specify the report path,the report file will be generated in the same directory as the csv file.
 
.OUTPUTS
 
None
#>


function Move-FlyMigrationMappings {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [String]
        ${Path},
        [Parameter(Mandatory = $false)]
        [String]
        ${OutFile}
    ) 
    Process {
        'Calling method: Move-FlyMigrationMappings' | Write-Debug
        $PSBoundParameters | Out-DebugParameter | Write-Debug
        $moveMappings = Get-FlyMappingsFromCsv -Path $Path
        if ($OutFile) {
            $isCsv = Confirm-FileExtension -Path $OutFile -AllowedExtensions 'csv'
            if (-not $isCsv) {
                throw 'The report file is not a CSV file.'
            }
        }
        $projectCache = @{}
        $mappingsCache = @{}
        $moveSettings = @{}
        $reports = @()
        foreach ($moveItem in $moveMappings) {
            $mapping = $moveItem.PSObject.Copy()
            $mapping | Add-Member -MemberType NoteProperty -Name 'Mapping ID' -Value $null
            $mapping | Add-Member -MemberType NoteProperty -Name 'Status' -Value ''
            $mapping | Add-Member -MemberType NoteProperty -Name 'Comment' -Value ''
            $reports += $mapping
            Try {
                $validateProperties = @('Source', 'Destination', 'Source project', 'Destination project')
                Confirm-PSCustomObjectProperties -Object $mapping -Properties $validateProperties
                if ($mapping.'Source project' -eq $mapping.'Destination project') {
                    $mapping.Status = 'Verification failed'
                    $mapping.Comment = 'The source and destination projects must be different.'
                    continue;
                }
                if (!$projectCache.ContainsKey($mapping.'Source project')) {
                    $projectCache[$mapping.'Source project'] = Get-FlyProjectByName -ProjectName $mapping.'Source project'
                }
                $oldProject = $projectCache[$mapping.'Source project']
                if ($null -eq $oldProject) {
                    $mapping.Status = 'Verification failed'
                    $mapping.Comment = 'Failed to retrieve the project: {0}.' -f $mapping.'Source project'
                    continue;
                }
                if (!$projectCache.ContainsKey($mapping.'Destination project')) {
                    $projectCache[$mapping.'Destination project'] = Get-FlyProjectByName -ProjectName $mapping.'Destination project'
                }
                $newProject = $projectCache[$mapping.'Destination project']
                if ($null -eq $newProject) {
                    $mapping.Status = 'Verification failed'
                    $mapping.Comment = 'Failed to retrieve the project: {0}.' -f $mapping.'Destination project'
                    continue;
                }
                $allMappings = $mappingsCache[$oldProject.Id]
                if ($null -eq $allMappings) {
                    $allMappings = Get-FlyAllProjectMappings -ProjectId $oldProject.Id
                    $mappingsCache[$oldProject.Id] = $allMappings
                }
                foreach ($item in $allMappings) {
                    $sourceIdentity = [System.Web.HttpUtility]::UrlDecode($mapping.'Source')
                    $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($mapping.'Destination')
                    if ($item.SourceIdentity -eq $sourceIdentity -and $item.DestinationIdentity -eq $destinationIdentity) {
                        $mapping.'Mapping ID' = $item.Id
                        if (!$moveSettings.ContainsKey($oldProject.Id + ';' + $newProject.Id)) {
                            $moveSettings[$oldProject.Id + ';' + $newProject.Id] = @()
                        }
                        if ($moveSettings[$oldProject.Id + ';' + $newProject.Id] -contains $item.Id) {
                            $mapping.Status = 'Verification failed'
                            $mapping.Comment = 'The mapping already exists in the file.'
                        }
                        else {
                            $moveSettings[$oldProject.Id + ';' + $newProject.Id] += $item.Id
                        }
                        break;
                    }
                }
                if ($null -eq $mapping.'Mapping ID') {
                    $mapping.Status = 'Verification failed'
                    $mapping.Comment = 'The mapping does not exist in the project: {0}.' -f $oldProject.Name
                }
            }
            Catch {
                if ($null -ne $_.ErrorDetails) {
                    Try {
                        $errorMessage = ConvertFrom-Json $_.ErrorDetails.Message
                        $mapping.Status = 'Verification failed'
                        $mapping.Comment = $errorMessage.errorMessage
                    }
                    Catch {
                    }
                }
                elseIf ($null -ne $_.Exception) {
                    $mapping.Status = 'Verification failed'
                    $mapping.Comment = $_.Exception.Message
                }
            }
        }
        foreach ($settingKey in $moveSettings.Keys) {
            if ($moveSettings[$settingKey].Length -eq 0) {
                continue;
            }
            $projectIds = $settingKey -split ';'
            $oldProjectId = $projectIds[0]
            $oldProject = $projectCache.Values | Where-Object { $_.Id -eq $oldProjectId } | Select-Object -First 1
            $oldProjectName = $oldProject.Name
            $newProjectId = $projectIds[1]
            $newProject = $projectCache.Values | Where-Object { $_.Id -eq $newProjectId } | Select-Object -First 1
            $newProjectName = $newProject.Name
            $mappingIds = $moveSettings[$settingKey]
            Try {
                $setting = [PSCustomObject]@{
                    'projectId'  = $newProjectId
                    'mappingIds' = $mappingIds
                }
                switch ($oldProject.SourcePlatform) {
                    0 { [void](Move-FlyExchangeMappings -ProjectId $oldProjectId -MoveMappingSettingsModel $setting) }
                    1 { [void](Move-FlyTeamsMappings -ProjectId $oldProjectId -MoveMappingSettingsModel $setting) }
                    2 { [void](Move-FlySharePointMappings -ProjectId $oldProjectId -MoveMappingSettingsModel $setting) }
                    3 { [void](Move-FlyOneDriveMappings -ProjectId $oldProjectId -MoveMappingSettingsModel $setting) }
                    4 { [void](Move-FlyM365GroupMappings -ProjectId $oldProjectId -MoveMappingSettingsModel $setting) }
                    5 { [void](Move-FlyTeamChatMappings -ProjectId $oldProjectId -MoveMappingSettingsModel $setting) }
                    Default { throw 'This workspace is not supported.' }
                }
                Write-Host ('Successfully moved {0} mappings from the project: {1} to the project: {2}' -f $mappingIds.Length, $oldProjectName, $newProjectName)   -ForegroundColor Green
                foreach ($mappingId in $mappingIds) {
                    $target = $reports | Where-Object { $_.'Mapping ID' -eq $mappingId } | Select-Object -First 1
                    if ($null -ne $target) {
                        $target.Status = 'Successful'
                        $target.Comment = ''
                    }
                }
            }
            Catch {
                if ($null -ne $_.ErrorDetails) {
                    Write-Host ('Failed to moved mappings from the project: {0} to the project: {1}. Details: {2}' -f $oldProjectName, $newProjectName, $_.ErrorDetails.Message)   -ForegroundColor Red
                    Try {
                        $errorMessage = ConvertFrom-Json $_.ErrorDetails.Message
                        foreach ($mappingId in $mappingIds) {
                            $target = $reports | Where-Object { $_.'Mapping ID' -eq $mappingId } | Select-Object -First 1
                            if ($null -ne $target) {
                                $target.Status = 'Failed'
                                $target.Comment = $errorMessage.errorMessage
                            }
                        }
                    }
                    Catch {
                    }
                }
                elseIf ($null -ne $_.Exception) {
                    Write-Host ('Failed to moved mappings from the project: {0} to the project: {1}. Details: {2}' -f $oldProjectName, $newProjectName, $_.Exception)   -ForegroundColor Red
                    if ($null -ne $_.Exception.Response -and $_.Exception.Response.StatusCode -eq 429) {
                        foreach ($mappingId in $mappingIds) {
                            $target = $reports | Where-Object { $_.'Mapping ID' -eq $mappingId } | Select-Object -First 1
                            if ($null -ne $target) {
                                $target.Status = ''
                                $target.Comment = ''
                            }
                        }
                        Write-Host ('Will try again after 60 seconds.')
                        Start-Sleep -Seconds 60
                    }
                    else {
                        foreach ($mappingId in $mappingIds) {
                            $target = $reports | Where-Object { $_.'Mapping ID' -eq $mappingId } | Select-Object -First 1
                            if ($null -ne $target) {
                                $target.Status = 'Failed'
                                $target.Comment = $_.Exception.Message
                            }
                        }
                    }
                }
            }
        }
        if (-not $OutFile) {
            $file = 'Fly_Move_Mappings_Report_{0}.csv' -f (Get-Date -Format "yyyyMMddHHmmss")
            $folder = Split-Path -Path $Path
            $OutFile = Join-Path $folder -ChildPath $file
        }
        $reports | Export-Csv -Path $OutFile -NoTypeInformation
        Write-Host ('Report path: {0}' -f $OutFile)
    }
}

<#
.SYNOPSIS
 
Generate error report for the specified projects
 
.DESCRIPTION
 
Generate error report for the specified projects
 
.PARAMETER Projects
Specify a list of project names to generate their error report.
 
.PARAMETER FileType
Specify the format of the generated report file, CSV(default) or Excel
 
.PARAMETER OutFolder
Specify the folder path of error report file to download
 
.PARAMETER TimeZoneOffset
Specify the UTC time offset of current browser. This value will be used to adjust time values when generating the report file.
 
.OUTPUTS
 
None
#>

function Export-FlyErrorReport {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [String[]]
        ${Projects},
        [Parameter(Mandatory = $true)]
        [String]
        ${OutFolder},
        [Parameter(Mandatory = $false)]
        [ValidateSet('CSV', 'Excel')]
        [String]
        ${FileType} = [ReportFileType]::CSV,
        [Parameter(Mandatory = $false)]
        [Int32]
        ${TimeZoneOffset}
    )

    Process {
        'Calling method: Export-FlyErrorReport' | Write-Debug
        $PSBoundParameters | Out-DebugParameter | Write-Debug
        Try {
            $targetProjects = $Projects | ForEach-Object { Get-FlyProjectByName $PSItem }
            $projectIds = $targetProjects | Select-Object -Property Id | ForEach-Object { "$($_.Id)" }
            #Construct the settings of the error report
            $reportSetting = [PSCustomObject]@{
                "reportFileType" = $FileType
                "projectIds"     = @($projectIds)
                "timeZone"       = $TimeZoneOffset
            }
            #Trigger the error report job and get the job id
            $jobId = Start-FlyErrorReportJob -GenerateProjectErrorReportSettingsModel $reportSetting
            #Monitor the job status and download the report file when job is finished
            while ($true) {
                Write-Host 'The report generation job is running.' -ForegroundColor Green
                Start-Sleep -Seconds 60
                $job = Get-FlyReportJobs -RequestBody @($jobId)
                $jobStatus = $job.data[0].Status
                if ($jobStatus -eq [MappingJobStatus]::Finished -or $jobStatus -eq [MappingJobStatus]::FinishedWithException) {
                    Write-Host 'The report generation job is finished.' -ForegroundColor Green
                    $result = Get-FlyReportUrl -JobId $jobId
                    if ($null -ne $result.ReportUrl) {
                        $fileName = Split-Path $([uri]$result.ReportUrl).AbsolutePath -Leaf
                        $filePath = Join-Path -Path $OutFolder -ChildPath $fileName
                        If (-not (Test-Path $OutFolder)) {
                            # Folder does not exist, create it
                            [void](New-Item -Path $OutFolder -ItemType Directory)
                        }
                        Invoke-WebRequest -URI $result.ReportUrl -OutFile $filePath -UseBasicParsing
                        Write-Host 'Successfully downloaded the job report. Report path:' $filePath -ForegroundColor Green
                    }
                    break;
                }
                elseif ($jobStatus -eq [MappingJobStatus]::Failed -or $jobStatus -eq [MappingJobStatus]::Stopped) {
                    throw ('The report generation job is {0} with id {1}' -f [MappingJobStatus].GetEnumName($jobStatus), $job.data[0].JobName)
                }
            }
        }
        Catch {
            Write-Host 'Failed to generate the error report.' -ForegroundColor Red
            ErrorDetail $_
            throw
        }
    }
}