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 -MigrationModuleType $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 .PARAMETER PlatformType The platform 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}, [Parameter(Mandatory = $false)] [String] [ValidateSet('Exchange', 'Teams', 'SharePoint', 'M365Group', 'TeamChat', 'OneDrive')] ${PlatformType} ) 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) { if ($PlatformType -and ($PlatformType -ne [PlatformType].GetEnumName($result.sourcePlatform))) { throw ('The type of the project "{0}" is not {1}.' -f $ProjectName, $PlatformType) } 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 (!$mappings.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 = Get-FlyMappingsFromCsv -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 } } function Get-FlyExchangeMappings { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${ProjectId}, [Parameter(Mandatory = $false)] [String] ${Mappings} ) Process { 'Calling method: Get-FlyExchangeMappings' | Write-Debug $result = New-Object System.Collections.ArrayList if ($Mappings) { $allMappings = Get-FlyAllProjectMappings -ProjectId $targetProject.Id $targetMappings = Get-FlyMappingsFromCsv -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') $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Destination') 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 } } function Get-FlyTeamsMappings { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${ProjectId}, [Parameter(Mandatory = $false)] [String] ${Mappings} ) Process { 'Calling method: Get-FlyTeamsMappings' | Write-Debug $result = New-Object System.Collections.ArrayList if ($Mappings) { $allMappings = Get-FlyAllProjectMappings -ProjectId $targetProject.Id $targetMappings = Get-FlyMappingsFromCsv -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 Team email address') $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Destination Team email address') 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 } } function Get-FlyM365GroupMappings { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${ProjectId}, [Parameter(Mandatory = $false)] [String] ${Mappings} ) Process { 'Calling method: Get-FlyM365GroupMappings' | Write-Debug $result = New-Object System.Collections.ArrayList if ($Mappings) { $allMappings = Get-FlyAllProjectMappings -ProjectId $targetProject.Id $targetMappings = Get-FlyMappingsFromCsv -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 Group email address') $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Destination Group email address') 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 } } function Get-FlyTeamChatMappings { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${ProjectId}, [Parameter(Mandatory = $false)] [String] ${Mappings} ) Process { 'Calling method: Get-FlyTeamChatMappings' | Write-Debug $result = New-Object System.Collections.ArrayList if ($Mappings) { $allMappings = Get-FlyAllProjectMappings -ProjectId $targetProject.Id $targetMappings = Get-FlyMappingsFromCsv -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 user') $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Destination user') 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 } } function Get-FlyOneDriveMappings { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${ProjectId}, [Parameter(Mandatory = $false)] [String] ${Mappings} ) Process { 'Calling method: Get-FlyOneDriveMappings' | Write-Debug $result = New-Object System.Collections.ArrayList if ($Mappings) { $allMappings = Get-FlyAllProjectMappings -ProjectId $targetProject.Id $targetMappings = Get-FlyMappingsFromCsv -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 user') $destinationIdentity = [System.Web.HttpUtility]::UrlDecode($target.'Destination user') 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 } } function Get-FlyMappingsFromCsv { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${Path} ) Process { 'Calling method: Get-FlyMappingsFromCsv' | Write-Debug $isCsv = Confirm-FileExtension -Path $Path -AllowedExtensions 'csv' if (-not $isCsv) { throw 'The file is not a CSV file.' } $result = Import-Csv -Path $Path if ($null -eq $result) { throw 'There is no data configured in the CSV file.' } return $result } } function Confirm-FileExtension { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [String] ${Path}, [Parameter(Mandatory = $true)] [String[]] ${AllowedExtensions} ) Process { 'Calling method: Confirm-FileExtension' | Write-Debug $extension = '' if ($Path) { $split = (Split-Path -Path $Path -Leaf).Split(".") if ($split -and $split.Count -gt 1) { $extension = $split[1] } } if ($AllowedExtensions -contains $extension) { return $true } else { return $false } } } function Confirm-PSCustomObjectProperties { param ( [Parameter(Mandatory = $true)] [PSCustomObject] ${Object}, [Parameter(Mandatory = $true)] [string[]] ${Properties} ) Process { 'Calling method: Confirm-PSCustomObjectProperties' | Write-Debug foreach ($property in $Properties) { if (($null -eq $Object.$property) -or ('' -eq $Object.$property)) { throw ('{0} cannot be empty.' -f $property) } } } } function Convert-FlyMappingStatus { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [PSCustomObject] ${Mapping} ) Process { 'Convert-FlyMappingStatus' | Write-Debug $progress = $Mapping.JobProgress $finalStatus = @([ProjectMappingItemStageStatus]::Successful, [ProjectMappingItemStageStatus]::Exceptioned, [ProjectMappingItemStageStatus]::Failed) if ($finalStatus -contains $Mapping.StageStatus) { $progress = 100 } return [PSCustomObject]@{ 'Source' = $Mapping.SourceIdentity 'Destination' = $Mapping.DestinationIdentity 'Stage' = Get-FlyMappingStage $Mapping.Stage 'Stage status' = Get-FlyMappingStageStatus $Mapping 'Job progress (%)' = $progress } } } function Get-FlyMappingStage { param ( [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [ProjectMappingItemStage] ${Stage} ) Process { 'Calling method: Get-FlyMappingStage' | Write-Debug switch ($Stage) { ([ProjectMappingItemStage]::CreateMapping) { 'New mapping' } ([ProjectMappingItemStage]::RunVerification) { 'Mapping verification' } ([ProjectMappingItemStage]::RunAssessment) { 'Scan source data' } ([ProjectMappingItemStage]::RunDataMigration) { 'Migration' } ([ProjectMappingItemStage]::EmailForwarding) { 'Email forwarding' } ([ProjectMappingItemStage]::KeepX500EmailAddress) { 'Add X500 email address to destination mailboxes' } Default { [ProjectMappingItemStage].GetEnumName($Stage) } } } } function Get-FlyMappingStageStatus { param ( [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [PSCustomObject] ${Mapping} ) Process { 'Calling method: Get-FlyMappingStageStatus' | Write-Debug if ($Mapping.StageStatus -eq [ProjectMappingItemStageStatus]::Waiting -and $Mapping.ScheduleTime -gt 0) { return 'Scheduled' } else { switch ([ProjectMappingItemStageStatus]$Mapping.StageStatus) { ([ProjectMappingItemStageStatus]::NotStart) { 'Not started' } ([ProjectMappingItemStageStatus]::Waiting) { 'In queue' } ([ProjectMappingItemStageStatus]::Queued) { 'In queue with priority' } ([ProjectMappingItemStageStatus]::InProgress) { 'In progress' } ([ProjectMappingItemStageStatus]::Successful) { 'Finished' } ([ProjectMappingItemStageStatus]::Exceptioned) { 'Exceptions' } ([ProjectMappingItemStageStatus]::Failed) { 'Failed' } ([ProjectMappingItemStageStatus]::Stopped) { 'Stopped' } Default { [ProjectMappingItemStageStatus].GetEnumName($Mapping.StageStatus) } } } } } <# .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 data 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 Convert the string value of type to TeamsChannelType .DESCRIPTION Convert the string value of type to TeamsChannelType .PARAMETER Level The string value of type .OUTPUTS TeamsChannelType, refer to TeamsChannelType.ps1 #> function Get-FlyTeamsChannelType { param ( [Parameter(Position = 0, ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [String] ${Type} ) Process { 'Calling method: Get-FlyTeamsChannelType' | Write-Debug switch ($Type) { 'Standard' { [TeamsChannelType]::Standard } 'Private' { [TeamsChannelType]::Private } 'Shared' { [TeamsChannelType]::Shared } Default { throw ('Invalid teams channel type') } } } } <# .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 -UseBasicParsing return (ConvertFrom-Json $Response).access_token } } function Get-IdentityServiceTokenByClientSecret { [CmdletBinding()] [OutputType([string])] Param( [Parameter(Mandatory)] [string]$IdentityServiceUri, [Parameter(Mandatory)] [string]$Scope, [Parameter(Mandatory)] [string]$ClientId, [Parameter(Mandatory)] [string]$ClientSecret ) PROCESS { 'Calling method: Get-IdentityServiceTokenByClientSecret' | Write-Debug $RequestUri = $IdentityServiceUri.TrimEnd('/') + "/connect/token" $Body = @{ grant_type = 'client_credentials' scope = $Scope client_id = $ClientId client_secret = $ClientSecret } $Response = Invoke-WebRequest -Uri $RequestUri -Method 'POST' -Body $Body -ErrorAction Stop -UseBasicParsing 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 } } function Convert-JWTtoken { [CmdletBinding()] [OutputType([System.Int64])] Param( [Parameter(Mandatory = $true)] [String]$token ) PROCESS { 'Calling method: Convert-JWTtoken' | Write-Debug #Validate as per https://tools.ietf.org/html/rfc7519 #Access and ID tokens are fine, Refresh tokens will not work if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Write-Error "Invalid token" -ErrorAction Stop } #Header $tokenheader = $token.Split(".")[0].Replace('-', '+').Replace('_', '/') #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 while ($tokenheader.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenheader += "=" } #Payload $tokenPayload = $token.Split(".")[1].Replace('-', '+').Replace('_', '/') #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" } #Convert to Byte array $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload) #Convert to string array $tokenArray = [System.Text.Encoding]::UTF8.GetString($tokenByteArray) #Convert from JSON to PSObject $result = $tokenArray | ConvertFrom-Json return $result } } |