Api/FlyCommon.ps1

<#
.SYNOPSIS
 
Check if the project name available
 
.DESCRIPTION
 
Check if the project name available
 
.PARAMETER ProjectName
The name of the project which you want to check
 
.OUTPUTS
 
Boolean, True if the project name is available
#>

function Resolve-FlyProjectName {
    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [String]
        ${ProjectName}
    )

    Process {
        'Calling method: Resolve-FlyProjectName' | Write-Debug
        $notExist = Invoke-FlyCheckProjectExist -Name $ProjectName
        if (!$notExist) {
            throw ('The project "{0}" already exists. Configure a unique name for the project.' -f $ProjectName)
        }
    }
}

<#
.SYNOPSIS
 
Get the connection by name
 
.DESCRIPTION
 
Get the connection by name
 
.PARAMETER ConnectionName
The name of the connection
 
.OUTPUTS
 
ConnectionSummaryModel, the object model of the connection
#>

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

    Process {
        'Calling method: Get-FlyConnectionByName' | Write-Debug
        $top = 200;
        $skip = 0;
        while ($true) {
            $connections = Get-FlyConnections -Search $ConnectionName -Top $top -Skip $skip
            $result = $connections.data | Where-Object { $_.Name -eq $ConnectionName } | Select-Object -First 1
            if ($null -ne $result) {
                return $result
            }
            if (!$connections.nextLink) {
                throw ('Failed to retrieve the connection: {0}. Log in to Fly to confirm the connection name.' -f $ConnectionName)
            }
            $skip = $skip + $top
        }
    }
}

<#
.SYNOPSIS
 
Get the policy by name
 
.DESCRIPTION
 
Get the policy by name
 
.PARAMETER PolicyName
The name of the policy
 
.OUTPUTS
 
PolicySummaryModel, the object model of the policy
#>

function Get-FlyPolicyByName {
    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [String]
        ${PolicyName},
        [Parameter(Mandatory = $true)]
        [PSCustomObject]
        ${PlatformType}
    )

    Process {
        'Calling method: Get-FlyPolicyByName' | Write-Debug
        $top = 200;
        $skip = 0;
        while ($true) {
            $policies = Get-FlyPolicies -PlatformType $PlatformType  -Search $PolicyName -Top $top -Skip $skip
            $result = $policies.data | Where-Object { $_.Name -eq $PolicyName } | Select-Object -First 1
            if ($null -ne $result) {
                return $result
            }
            if (!$policies.nextLink) {
                throw ('Failed to retrieve the migration policy: {0}. Log in to Fly to confirm the policy name.' -f $PolicyName)
            }
            $skip = $skip + $top
        }
    }
}

<#
.SYNOPSIS
 
Get the tag by name
 
.DESCRIPTION
 
Get the tag by name
 
.PARAMETER TagName
The name of the tag
 
.OUTPUTS
 
TagSummaryModel, the object model of the tag
#>

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

    Process {
        'Calling method: Get-FlyTagByName' | Write-Debug
        $top = 200;
        $skip = 0;
        while ($true) {
            $tags = Get-FlyTags -Search $TagName -Top $top -Skip $skip
            $result = $tags.data | Where-Object { $_.Name -eq $TagName } | Select-Object -First 1
            if ($null -ne $result) {
                return $result
            }
            if (!$tags.nextLink) {
                throw ('Failed to retrieve the tag: {0}. Log in to Fly to confirm the tag name.' -f $TagName)
            }
            $skip = $skip + $top
        }
    }
}

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

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

    Process {
        'Calling method: Get-FlyProjectByName' | Write-Debug
        $top = 200;
        $skip = 0;
        while ($true) {
            $projects = Get-FlyProjects -Search $ProjectName -Top $top -Skip $skip
            $result = $projects.data | Where-Object { $_.Name -eq $ProjectName } | Select-Object -First 1
            if ($null -ne $result) {
                return $result
            }
            if (!$projects.nextLink) {
                throw ('Failed to retrieve the project: {0}. Log in to Fly to confirm the project name.' -f $ProjectName)
            }
            $skip = $skip + $top
        }
    }
}

<#
.SYNOPSIS
 
Get all project mappings by projectId
 
.DESCRIPTION
 
Get all project mappings by projectId
 
.PARAMETER ProjectName
The GUID of the project
 
.OUTPUTS
 
A list of the ProjectMappingSummaryModel
#>

function Get-FlyAllProjectMappings {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [String]
        ${ProjectId}
    )

    Process {
        'Calling method: Get-FlyAllProjectMappings' | Write-Debug
        $top = 2000;
        $skip = 0;
        $result = New-Object System.Collections.ArrayList;
        while ($true) {
            $mappings = Get-FlyProjectMappings -ProjectId $ProjectId -Top $top -Skip $skip
            if ($null -ne $mappings.data) {
                $result.AddRange($mappings.data)
            }
            if (!$projects.nextLink) {
                break;
            }
            $skip = $skip + $top
        }
        return $result
    }
}

function Get-FlySharePointMappings {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [String]
        ${ProjectId},
        [Parameter(Mandatory = $false)]
        [String]
        ${Mappings}
    )

    Process {
        'Calling method: Get-FlySharePointMappings' | Write-Debug
        $result = New-Object System.Collections.ArrayList
        if ($Mappings) {
            $allMappings = Get-FlyAllProjectMappings -ProjectId $targetProject.Id
            $targetMappings = Import-Csv -Path $Mappings
            #Match the project mappings between csv file and specified project
            foreach ($target in $targetMappings) {
                foreach ($mapping in $allMappings) {
                    $sourceIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Source URL')
                    $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Destination URL')
                    if ($mapping.SourceIdentity -eq $sourceIdentity -and $mapping.DestinationIdentity -eq $destinationIdentity) {
                        [void]$result.Add($mapping);
                        break;
                    }
                }
            }
            if ($result.Count -eq 0) {
                throw 'No mapping in the CSV file matches the existing migration mappings in this project.'
            }
        }
        return $result
    }
}

<#
.SYNOPSIS
 
Convert the string value of object level to integer
 
.DESCRIPTION
 
Convert the string value of object level to integer
 
.PARAMETER Level
The string value of object level
 
.OUTPUTS
 
The integer value of object level
#>

function Get-FlyDataType {
    param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [String]
        ${Level}
    )
    Process {
        'Calling method: Get-FlyDataType' | Write-Debug
        switch ($Level) {
            'Site collection' { 600 }
            'Site' { 400 }
            'List' { 200 }
            'Folder' { 100 }
            'User mailbox' { 1001 }
            'Archive mailbox' { 1002 }
            'Distribution list' { 1007 }
            'Microsoft 365 Group mailbox' { 1003 }
            'Resource mailbox' { 1004 }
            'Shared mailbox' { 1005 }
            'Mail-enabled security group' { 1008 }
            'Microsoft 365 Group' { 1006 }
            Default { throw ('invalid type') }
            
        }
    }
}

<#
.SYNOPSIS
 
Convert the string value of method to SharePointMethodTypes, only support SharePoint object for now
 
.DESCRIPTION
 
Convert the string value of method to SharePointMethodTypes, only support SharePoint object for now
 
.PARAMETER Level
The string value of method
 
.OUTPUTS
 
SharePointMethodTypes, refer to SharePointMethodTypes.ps1
#>

function Get-FlySharePointMethod {
    param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [String]
        ${Method}
    )
    Process {
        'Calling method: Get-FlySharePointMethod' | Write-Debug
        switch ($Method) {
            'Merge' { [SharePointMethodTypes]::Combine }
            'Attach' { [SharePointMethodTypes]::Attach }
            Default { throw ('invalid method') }
        }
    }
}

<#
.SYNOPSIS
 
Output the error details of ErrorRecord
 
.DESCRIPTION
 
Output the error details of ErrorRecord
 
.PARAMETER Level
The information of ErrorRecord
 
.OUTPUTS
 
#>

function ErrorDetail {
    [CmdletBinding()]
    Param (
        [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
        [System.Management.Automation.ErrorRecord]
        ${Error}
    )

    Process {
        'Calling method: ErrorDetail' | Write-Debug
        if ($Error.ErrorDetails.Message) {
            Write-Host $Error.ErrorDetails.Message -ForegroundColor Red
        }
        Write-Error $Error.Exception
    }
}

function Get-IdentityServiceToken {
    [CmdletBinding()]
    [OutputType([string])]
    Param(
        [Parameter(Mandatory)]
        [string]$IdentityServiceUri,
        [Parameter(Mandatory)]
        [string]$Scope,
        [Parameter(Mandatory)]
        [string]$ClientId,
        [Parameter(Mandatory)]
        [Alias("Certificate", "Cert")]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningCertificate
    )
    PROCESS {
        'Calling method: Get-IdentityServiceToken' | Write-Debug
        $encodedThumbprint = ConvertTo-Base64UrlEncodedString -Bytes $SigningCertificate.GetCertHash()
        $headerTable = [ordered]@{typ = "JWT"; alg = "RS256"; kid = $encodedThumbprint }
        $header = $headerTable | ConvertTo-Json -Compress | ConvertTo-Base64UrlEncodedString
        $now = Get-Date
        $currentEpochTime = Convert-DateTimeToEpoch -DateTime $now
        $notBefore = $currentEpochTime
        $futureEpochTime = Convert-DateTimeToEpoch -DateTime ($now.AddHours(1))
        $payloadTable = [ordered]@{sub = $ClientId; jti = ([System.Guid]::NewGuid()).ToString(); iss = $ClientId; aud = $IdentityServiceUri.TrimEnd('/') + "/connect/token"; nbf = $notBefore; exp = $futureEpochTime; iat = $currentEpochTime }
        $payload = $payloadTable | ConvertTo-Json -Compress | ConvertTo-Base64UrlEncodedString
        $jwtPlainText = "{0}.{1}" -f $header, $payload
        $jwtSig = New-JwtRsaSignature -JsonWebToken $jwtPlainText -SigningCertificate $SigningCertificate
        $ClientAssertion = "{0}.{1}" -f $jwtPlainText, $jwtSig
        $RequestUri = $IdentityServiceUri.TrimEnd('/') + "/connect/token"
        $Body = @{
            grant_type            = 'client_credentials'
            scope                 = $Scope
            client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
            client_assertion      = $ClientAssertion
        }
        $Response = Invoke-WebRequest -Uri $RequestUri -Method 'POST' -Body $Body -ErrorAction Stop
        return (ConvertFrom-Json $Response).access_token
    }
}

function New-JwtRsaSignature {
    [CmdletBinding()]
    [OutputType([string])]
    Param(
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$SigningCertificate,
        [String]$JsonWebToken
    )
    PROCESS {
        'Calling method: New-JwtRsaSignature' | Write-Debug
        $rsa = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($SigningCertificate))
        if ($null -eq $rsa) {
            # Requiring the private key to be present
            throw "There's no private key in the supplied certificate." 
        }
        [byte[]]$message = [System.Text.Encoding]::UTF8.GetBytes($JsonWebToken)
        $sigBytes = $rsa.SignData($message, [Security.Cryptography.HashAlgorithmName]::SHA256, [Security.Cryptography.RSASignaturePadding]::Pkcs1)
        return ConvertTo-Base64UrlEncodedString -Bytes $sigBytes
    }
}


function ConvertTo-Base64UrlEncodedString {
    [CmdletBinding()]
    [OutputType([string])]
    Param (
        [Parameter(Position = 0, ParameterSetName = "String", Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$InputString,

        [Parameter(Position = 1, ParameterSetName = "Byte Array", Mandatory = $false, ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false)]
        [byte[]]$Bytes
    )
    PROCESS {
        [string]$base64UrlEncodedString = ""

        if ($PSBoundParameters.ContainsKey("Bytes")) {
            $output = [Convert]::ToBase64String($Bytes)
            $output = $output.Split('=')[0] # Remove any trailing '='s
            $output = $output.Replace('+', '-') # 62nd char of encoding
            $output = $output.Replace('/', '_') # 63rd char of encoding
            $base64UrlEncodedString = $output

        }
        else {
            $encoder = [System.Text.UTF8Encoding]::new()
            [byte[]]$inputBytes = $encoder.GetBytes($InputString)
            $base64String = [Convert]::ToBase64String($inputBytes)
            [string]$base64UrlEncodedString = ""
            $base64UrlEncodedString = $base64String.Split('=')[0] # Remove any trailing '='s
            $base64UrlEncodedString = $base64UrlEncodedString.Replace('+', '-'); # 62nd char of encoding
            $base64UrlEncodedString = $base64UrlEncodedString.Replace('/', '_'); # 63rd char of encoding
        }
        return $base64UrlEncodedString
    }
}

function Convert-DateTimeToEpoch {
    [CmdletBinding()]
    [OutputType([System.Int64])]
    Param(
        [Parameter(Mandatory)]
        [DateTime]$DateTime
    )
    PROCESS {
        'Calling method: Convert-DateTimeToEpoch' | Write-Debug
        $dtut = $DateTime.ToUniversalTime()
        [TimeSpan]$ts = New-TimeSpan -Start  (Get-Date "01/01/1970") -End $dtut
        [Int64]$secondsSinceEpoch = [Math]::Floor($ts.TotalSeconds)
        return $secondsSinceEpoch
    }
}