MSGraph.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\MSGraph.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName MSGraph.Import.DoDotSource -Fallback $false
if ($MSGraph_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName MSGraph.Import.IndividualFiles -Fallback $false
if ($PoShPRTG_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }

function Import-ModuleFile {
    <#
        .SYNOPSIS
            Loads files into the module on module import.
 
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
 
            This provides a central location to react to files being imported, if later desired
 
        .PARAMETER Path
            The path to the file to load
 
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
 
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )

    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) {
        . $resolvedPath
    } else {
        $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null)
    }
}

#region Load individual files
if ($importIndividualFiles) {
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }

    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) {
        . Import-ModuleFile -Path $function.FullName
    }

    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) {
        . Import-ModuleFile -Path $function.FullName
    }

    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }

    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
<#
This file loads the strings documents from the respective language folders.
This allows localizing messages and errors.
Load psd1 language files for each language you wish to support.
Partial translations are acceptable - when missing a current language message,
it will fallback to English or another available language.
#>

Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'MSGraph' -Language 'en-US'

Add-Type -AssemblyName System.Net.Http
Add-Type -AssemblyName System.Web
Add-Type -AssemblyName System.Windows.Forms

function Merge-HashToJson {
    <#
    .SYNOPSIS
        Merge a hashtable(s) object to a JSON data string(s)
 
    .DESCRIPTION
        Merge a hashtable(s) object to a JSON data string(s)
        Accepts [hashtable] object(s) as well as [System.Collections.Specialized.OrderedDictionary] object(s)
 
        Helper function used for internal commands.
 
    .PARAMETER Hashtable
        The hashtable to convert to json
 
    .PARAMETER OrderedHashtable
        A hash created by [ordered]@{} to convert to json
 
    .EXAMPLE
        PS C:\> Merge-HashToJson $hash
 
        Creates a json string from content in variable $hash.
        This is the recommend usage
 
        Variable $hash can be:
            $hash = @{ content = "this is a regular hashtable" }
        or
            $hash = [ordered]@{ content = "this is a ordered hashtable" }
 
    .EXAMPLE
        PS C:\> Merge-HashToJson -Hashtable $hash
 
        Creates a json string from content in variable $hash.
        Variable $hash has to be a regular hashtable:
            $hash = @{ content = "this is a regular hashtable" }
 
    .EXAMPLE
        PS C:\> Merge-HashToJson -OrderedHashtable $hash
 
        Creates a json string from content in variable $hash.
        Variable $hash has to be a ordered hashtable:
            $hash = @{ content = "this is a regular hashtable" }
 
    #>

    #[CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = "OrderedHash")]
    [CmdletBinding(ConfirmImpact = 'Low')]
    [OutputType([String])]
    param (
        [Parameter(ParameterSetName = "HashTable", Position = 0, Mandatory = $true)]
        [hashtable[]]
        $Hashtable,

        [Parameter(ParameterSetName = "OrderedHash", Position = 0, Mandatory = $true)]
        [System.Collections.Specialized.OrderedDictionary[]]
        $OrderedHashtable
    )

    begin {
    }

    process {
        if ($PSCmdlet.ParameterSetName -like "OrderedHash") {
            $Hashtable = [ordered]@{}
            $Hashtable = $OrderedHashtable
        }

        Write-PSFMessage -Level Debug -Message "Merge hashtable with key(s) ('$([string]::Join("', '", $Hashtable.Keys))') by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        foreach ($hash in $Hashtable) {
            $JsonParts = @()

            foreach ($key in $hash.Keys) {
                $JsonParts = $JsonParts + """$($key)"" : $($hash[$key])"
            }
            $json = "{`n" + ([string]::Join(",`n", $JsonParts)) + "`n}"

            $json
        }
    }

    end {
    }
}


function Convert-UriQueryFromHash {
    <#
    .SYNOPSIS
        Converts hashtables to a string for REST api calls.
 
    .DESCRIPTION
        Converts hashtables to a string for REST api calls.
 
    .PARAMETER hash
        The hashtable to convert to a string
 
    .PARAMETER NoQuestionmark
        Supress the ? as the first character in the output string
 
    .EXAMPLE
        PS C:\> Convert-UriQueryFromHash -Hash @{ username = "user"; password = "password"}
 
        Converts the specified hashtable to the following string:
        ?password=password&username=user
 
    .EXAMPLE
        PS C:\> Convert-UriQueryFromHash -Hash @{ username = "user"; password = "password"} -NoQuestionmark
 
        Converts the specified hashtable to the following string:
        password=password&username=user
        #>

    [OutputType([System.String])]
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [System.Collections.Hashtable]
        $Hash,

        [switch]
        $NoQuestionmark
    )

    begin {
    }

    process {
        $elements = foreach ($key in $Hash.Keys) {
            $key + "=" + $Hash[$key]
        }
        $elementString = [string]::Join("&", $elements)

        if ($NoQuestionMark) {
            "$elementString"
        } else {
            "?$elementString"
        }
    }

    end {
    }
}

function ConvertFrom-Base64StringWithNoPadding( [string]$Data ) {
    <#
    .SYNOPSIS
        Helper function build valid Base64 strings from JWT access tokens
 
    .DESCRIPTION
        Helper function build valid Base64 strings from JWT access tokens
 
    .PARAMETER Data
        The Token to convert
 
    .EXAMPLE
        PS C:\> ConvertFrom-Base64StringWithNoPadding -Data $data
 
        build valid base64 string the content from variable $data
    #>

    $Data = $Data.Replace('-', '+').Replace('_', '/')
    switch ($Data.Length % 4) {
        0 { break }
        2 { $Data += '==' }
        3 { $Data += '=' }
        default { throw New-Object ArgumentException('data') }
    }
    [System.Convert]::FromBase64String($Data)
}

function ConvertFrom-JWTtoken {
    <#
    .SYNOPSIS
        Converts access tokens to readable objects
 
    .DESCRIPTION
        Converts access tokens to readable objects
 
    .PARAMETER Token
        The Token to convert
 
    .EXAMPLE
        PS C:\> ConvertFrom-JWTtoken -Token $Token
 
        Converts the content from variable $token to an object
    #>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Token
    )

    # Validate as per https://tools.ietf.org/html/rfc7519 - Access and ID tokens are fine, Refresh tokens will not work
    if ((-not $Token.Contains(".")) -or (-not $Token.StartsWith("eyJ"))) {
        $msg = "Invalid data or not an access token. $($Token)"
        Stop-PSFFunction -Message $msg -Tag "JWT" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
    }

    # Split the token in its parts
    $tokenParts = $Token.Split(".")

    # Work on header
    $tokenHeader = [System.Text.Encoding]::UTF8.GetString( (ConvertFrom-Base64StringWithNoPadding $tokenParts[0]) )
    $tokenHeaderJSON = $tokenHeader | ConvertFrom-Json

    # Work on payload
    $tokenPayload = [System.Text.Encoding]::UTF8.GetString( (ConvertFrom-Base64StringWithNoPadding $tokenParts[1]) )
    $tokenPayloadJSON = $tokenPayload | ConvertFrom-Json

    # Work on signature
    $tokenSignature = ConvertFrom-Base64StringWithNoPadding $tokenParts[2]

    # Output
    $resultObject = New-Object MSGraph.Core.JWTAccessTokenInfo

    $resultObject.Header = $tokenHeader
    $resultObject.Payload = $tokenPayload
    $resultObject.Signature = $tokenSignature
    $resultObject.Algorithm = $tokenHeaderJSON.alg
    $resultObject.Type = $tokenHeaderJSON.typ
    if ($tokenPayloadJSON.appid) { $resultObject.ApplicationID = $tokenPayloadJSON.appid }
    $resultObject.ApplicationName = $tokenPayloadJSON.app_displayname
    $resultObject.Issuer = $tokenPayloadJSON.iss
    $resultObject.Audience = $tokenPayloadJSON.aud
    $resultObject.AuthenticationMethod = $tokenPayloadJSON.amr
    $resultObject.ExpirationTime = ([datetime]"1970-01-01Z00:00:00").AddSeconds($tokenPayloadJSON.exp).ToUniversalTime()
    $resultObject.GivenName = $tokenPayloadJSON.given_name
    $resultObject.IssuedAt = ([datetime]"1970-01-01Z00:00:00").AddSeconds($tokenPayloadJSON.iat).ToUniversalTime()
    $resultObject.Name = $tokenPayloadJSON.name
    $resultObject.NotBefore = ([datetime]"1970-01-01Z00:00:00").AddSeconds($tokenPayloadJSON.nbf).ToUniversalTime()
    if ($tokenPayloadJSON.oid) { $resultObject.OID = $tokenPayloadJSON.oid }
    $resultObject.Plattform = $tokenPayloadJSON.platf
    $resultObject.Scope = $tokenPayloadJSON.scp
    $resultObject.SID = $tokenPayloadJSON.onprem_sid
    $resultObject.SourceIPAddr = $tokenPayloadJSON.ipaddr
    $resultObject.SureName = $tokenPayloadJSON.family_name
    $resultObject.TenantID = $tokenPayloadJSON.tid
    $resultObject.UniqueName = $tokenPayloadJSON.unique_name
    $resultObject.UPN = $tokenPayloadJSON.upn
    $resultObject.Version = $tokenPayloadJSON.ver

    #$output
    $resultObject
}

function Invoke-TokenLifetimeValidation {
    <#
    .SYNOPSIS
        Validates the lifetime of a token object
 
    .DESCRIPTION
        Validates the lifetime of a token object and invoke update-token process, if needed.
        Helper function used for internal commands.
 
    .PARAMETER Token
        The Token to test and receive
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> Resolve-Token -User $Token
 
        Test Token for lifetime, or receives registered token from script variable
    #>

    [OutputType([MSGraph.Core.AzureAccessToken])]
    [CmdletBinding()]
    param (
        [MSGraph.Core.AzureAccessToken]
        $Token,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    process {
        $Token = Resolve-Token -Token $Token -FunctionName $FunctionName

        if ( (-not $Token.IsValid) -or ($Token.PercentRemaining -lt 15) ) {
            # if token is invalid or less then 15 percent of lifetime -> go and refresh the token
            Write-PSFMessage -Level Verbose -Message "Token lifetime is less then 15%. Initiate token refresh. Time remaining $($Token.TimeRemaining)" -Tag "Authentication" -FunctionName $FunctionName
            $paramsTokenRefresh = @{
                Token    = $Token
                PassThru = $true
            }
            if ($script:msgraph_Token.AccessTokenInfo.Payload -eq $Token.AccessTokenInfo.Payload) { $paramsTokenRefresh.Add("Register", $true) }
            if ($Token.Credential) { $paramsTokenRefresh.Add("Credential", $Token.Credential) }
            $Token = Update-MgaAccessToken @paramsTokenRefresh
        } else {
            Write-PSFMessage -Level Verbose -Message "Valid token for user $($Token.UserprincipalName) - Time remaining $($Token.TimeRemaining)" -Tag "Authentication" -FunctionName $FunctionName
        }

        $Token
    }
}

function Invoke-TokenScopeValidation {
    <#
    .SYNOPSIS
        Validates the scope of a token object
 
    .DESCRIPTION
        Validates the scope of a token object and invoke update-token process, if needed.
        Helper function used for internal commands.
 
    .PARAMETER Token
        The Token to test.
 
    .PARAMETER Scope
        The scope(s) the check for existence.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .EXAMPLE
        PS C:\> $Token = Invoke-TokenScopeValidation -User $Token -Scope "Mail.Read"
 
        Test Token for scope and return the token. If necessary, the token will be renewed
    #>

    [OutputType([MSGraph.Core.AzureAccessToken])]
    [CmdletBinding()]
    param (
        [MSGraph.Core.AzureAccessToken]
        $Token,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Scope,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    process {
        $Token = Resolve-Token -Token $Token -FunctionName $FunctionName

        if (-not (Test-TokenScope -Token $Token -Scope $requiredPermission -FunctionName $FunctionName)) {
            # required scope information are missing in token
            Write-PSFMessage -Level Warning -Message "Required scope information ($([String]::Join(", ",$Scope))) are missing in token." -Tag "Authentication" -FunctionName $FunctionName
            if ($Token.IdentityPlatformVersion -like '2.0') {
                # With Microsoft Identity Platform 2.0 it is possible to dynamically query new scope informations (incremental consent)
                Write-PSFMessage -Level Verbose -Message "Microsoft Identity Platform 2.0 is used. Dynamical permission request possible. Try to aquire new token." -Tag "Authentication" -FunctionName $FunctionName

                $Scope = $Scope + $Token.Scope
                $tenant = if ($Token.TenantID -like "9188040d-6c67-4c5b-b112-36a304b66dad") {'consumers'} else {'common'}

                # build parameters to query new token
                $paramsNewToken = @{
                    PassThru                = $true
                    ClientId                = $Token.ClientId.ToString()
                    RedirectUrl             = $Token.AppRedirectUrl.ToString()
                    ResourceUri             = $Token.Resource.ToString().TrimEnd('/')
                    IdentityPlatformVersion = $Token.IdentityPlatformVersion
                    Permission              = ($Scope | Where-Object { $_ -notin "offline_access", "openid", "profile", "email" })
                    Tenant                  = $tenant
                }
                if ($script:msgraph_Token.AccessTokenInfo.Payload -eq $Token.AccessTokenInfo.Payload) {
                    $paramsNewToken.Add("Register", $true)
                }
                if ($Token.Credential) {
                    $paramsNewToken.Add("Credential", $Token.Credential)
                }

                $Token = New-MgaAccessToken @paramsNewToken
            } else {
                Stop-PSFFunction -Message "FAILED, missing required scope information ($([String]::Join(", ",$Scope))) and Microsoft Identity Platform 1.0 is used.`nNo dynamic permission request available. Permissions has to be specified/granted in app registration process or portal." -EnableException $true -Category AuthenticationError -FunctionName $FunctionName
            }
        } else {
            Write-PSFMessage -Level VeryVerbose -Message "OK, required scope information are present. ($([String]::Join(", ",$Scope)))" -Tag "Authentication" -FunctionName $FunctionName
        }

        $Token
    }
}

function New-HttpClient {
    <#
    .SYNOPSIS
        Generates a HTTP Client.
 
    .DESCRIPTION
        Generates a HTTP Client for use with web services (REST Api).
 
    .PARAMETER UserAgentName
        The name of the UserAgent.
 
    .PARAMETER UserAgentVersion
        The Version of the UserAgent.
 
    .PARAMETER HeaderType
        Data language in the header.
 
    .EXAMPLE
        PS C:\> New-HttpClient
 
        Creates a Http Client with default values
 
    .EXAMPLE
        PS C:\> New-HttpClient -UserAgentName "PowerShellRestClient" -userAgentVersion "1.1"
 
        Creates a Http Client with UserAgent "PowerShellRestClient" as name and Version 1.1.
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        [Alias('UserAgent')]
        [String]
        $UserAgentName = (Get-PSFConfigValue -FullName MSGraph.WebClient.UserAgentName -Fallback "PowerShellRestClient"),

        [Alias('Version')]
        [String]
        $userAgentVersion = (Get-PSFConfigValue -FullName MSGraph.WebClient.UserAgentVersion -Fallback "1.1"),

        [String]
        $HeaderType = "application/json"
    )

    process {
        $header = New-Object System.Net.Http.Headers.MediaTypeWithQualityHeaderValue($HeaderType)
        $userAgent = New-Object System.Net.Http.Headers.ProductInfoHeaderValue($UserAgentName, $userAgentVersion)

        $handler = New-Object System.Net.Http.HttpClientHandler
        $handler.CookieContainer = New-Object System.Net.CookieContainer
        $handler.AllowAutoRedirect = $true

        $httpClient = New-Object System.Net.Http.HttpClient($handler)
        $httpClient.Timeout = New-Object System.TimeSpan(0, 0, 90)
        $httpClient.DefaultRequestHeaders.TransferEncodingChunked = $false
        $httpClient.DefaultRequestHeaders.Accept.Add($header)
        $httpClient.DefaultRequestHeaders.UserAgent.Add($userAgent)

        return $httpClient
    }
}

function Resolve-Token {
    <#
    .SYNOPSIS
        Test for specified Token, or receives registered token
 
    .DESCRIPTION
        Test for specified Token, or receives registered token.
        Helper function used for internal commands.
 
    .PARAMETER Token
        The Token to test and receive
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .EXAMPLE
        PS C:\> Resolve-Token -User $Token
 
        Test Token for lifetime, or receives registered token from script variable
    #>

    [OutputType([MSGraph.Core.AzureAccessToken])]
    [CmdletBinding()]
    param (
        [MSGraph.Core.AzureAccessToken]
        $Token,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    process {
        if (-not $Token) {
            Write-PSFMessage -Level Debug -Message "No token on parameter in command. Getting registered token." -Tag "Authentication" -FunctionName $FunctionName
            $Token = $script:msgraph_Token
        }

        if ($Token) {
            $Token
        } else {
            Stop-PSFFunction -Message "Not connected! Use New-MgaAccessToken to create a Token and either register it or specifs it" -EnableException $true -Category AuthenticationError -FunctionName $FunctionName
        }
    }
}

function Resolve-UserString {
    <#
    .SYNOPSIS
        Converts usernames or email addresses into the user targeting segment of the Rest Api call url.
 
    .DESCRIPTION
        Converts usernames or email addresses into the user targeting segment of the Rest Api call url.
 
    .PARAMETER User
        The user to convert
 
    .PARAMETER ContextData
        Specifies, that the user string should be resolved to a @odata.context field
        Different output is needed on context URLs.
 
    .EXAMPLE
        PS C:\> Resolve-UserString -User $User
 
        Resolves $User into a legitimate user targeting string element.
 
        .EXAMPLE
        PS C:\> Resolve-UserString -User $User -ContextData
 
        Resolves $User into a legitimate user string for a @odata.context element.
    #>

    [OutputType([System.String])]
    [CmdletBinding()]
    param (
        [string]
        $User,

        [switch]
        $ContextData
    )

    if ($User -eq 'me' -or (-not $User)) {
        return 'me'
    }

    if($ContextData) {
        if ($User -like "users('*") {
            return $User
        } else {
            $userEscaped = [uri]::EscapeDataString($User)
            return "users('$($userEscaped)')"
        }
    } else {
        if ($User -like "users/*") {
            return $User
        } else {
            return "users/$($User)"
        }
    }
}

function Show-OAuthWindow {
    <#
    .SYNOPSIS
        Generates a OAuth window for interactive authentication.
 
    .DESCRIPTION
        Generates a OAuth window for interactive authentication.
 
    .PARAMETER Url
        The url to the service offering authentication.
 
    .EXAMPLE
        PS C:\> Show-OAuthWindow -Url $uri
 
        Opens an authentication window to authenticate against the service pointed at in $uri
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.Uri]
        $Url
    )

    begin {}

    process {}

    end {
        # check screen resultion and calculate size for login form
        $screenResolution = Get-CimInstance -ClassName Win32_VideoController
        $formWidth = [math]::round(($screenResolution.CurrentHorizontalResolution / 4.36), 0)
        $formHeight = [math]::round(($screenResolution.CurrentVerticalResolution / 1.69), 0)
        if ($formWidth -lt 440) { $formWidth = 440 }
        if ($formHeight -lt 640) { $formHeight = 640 }

        # Create form object
        $form = New-Object -TypeName "System.Windows.Forms.Form" -Property @{
            Width  = $formWidth #440
            Height = $formHeight #640
        }

        # Create web browser object
        $web = New-Object -TypeName "System.Windows.Forms.WebBrowser" -Property @{
            Url                    = $Url
            ClientSize             = $form.ClientSize
            ScriptErrorsSuppressed = $true
        }

        #region Event actions
        # parse code or error message from URL, when Login is completed
        $web.Add_DocumentCompleted( {
                if ($web.Url.AbsoluteUri -match "error=[^&]*|code=[^&]*") { $form.Close() }
            } )

        # Things to do when form is opened/shown
        $form.Add_Shown( {
                $form.BringToFront()
                $null = $form.Focus()
                $form.Activate()
                $web.Navigate($Url)
                $form.Text = $web.DocumentTitle
            } )

        # make form resizeable
        $form.Add_Resize( {
                $web.ClientSize = $form.ClientSize
                $form.Text = $web.DocumentTitle
            } )
        #endregion Event actions

        # Add browser to windows form
        $form.Controls.Add($web)

        # Show form to the user
        $null = $form.ShowDialog()

        # Get result from uri (query string within the uri)
        $queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
        $output = @{}
        foreach ($key in $queryOutput.Keys) {
            $output["$key"] = $queryOutput[$key]
        }

        # output result
        [pscustomobject]$output
    }
}

function Test-TokenScope {
    <#
    .SYNOPSIS
        Test for scopes existence on a Token
 
    .DESCRIPTION
        Test for existence on scopes (permissions) in a Token
        Helper function used for internal commands.
 
    .PARAMETER Token
        The Token to test.
 
    .PARAMETER Scope
        The scope(s) the check for existence.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .EXAMPLE
        PS C:\> Test-TokenScope -User $Token -Scope "Mail.Read"
 
        Test if the specified Token contains scope "Mail.Read"
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [MSGraph.Core.AzureAccessToken]
        $Token,

        [Parameter(Mandatory = $true)]
        [string[]]
        $Scope,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    begin {
        $Status = $false
    }

    process {
        $Token = Resolve-Token -Token $Token -FunctionName $MyInvocation.MyCommand

        Write-PSFMessage -Level VeryVerbose -Message "Validating token scope ($([String]::Join(", ",$Token.Scope))) against specified scope(s) ($([String]::Join(", ",$Scope)))" -Tag "Authenication" -FunctionName $FunctionName
        foreach ($scopeName in $Scope) {
            foreach ($tokenScope in $Token.Scope) {
                if ($tokenScope -like "$scopeName*") {
                    Write-PSFMessage -Level Debug -Message "Token has appropriate scope ($($scopeName))" -Tag "Authenication" -FunctionName $FunctionName
                    $Status = $true
                }
            }
        }
    }

    end {
        $Status
    }
}

function Resolve-MailObjectFromString {
    <#
    .SYNOPSIS
        Resolves a name/id from a mail or folder parameter class
 
    .DESCRIPTION
        Resolves a name/id from a mail or folder parameter class to a full qualified mail or folder object and return the parameter class back.
        Helper function used for internal commands.
 
    .PARAMETER Object
        The mail or folder object
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER NoNameResolving
        If specified, there will be no checking on names. Only Id will be resolved.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> Resolve-MailObjectFromString -Object $MailFolder -User $User -Token $Token -Function $MyInvocation.MyCommand
 
        Resolves $MailFolder into a legitimate user targeting string element.
    #>

    [OutputType()]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    param (
        $Object,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $NoNameResolving,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    # variable definition
    $invokeParam = @{
        "User"  = $User
        "Token" = $Token
    }

    # check input object type
    if ($Object.psobject.TypeNames[0] -like "MSGraph.Exchange.Mail.FolderParameter") {
        $type = "Folder"
        $typeNamespace = "MSGraph.Exchange.Mail"
        $nounPreFix = "MgaMail"
        $parameterName = "InputObject"
    } elseif ($Object.psobject.TypeNames[0] -like "MSGraph.Exchange.Mail.MessageParameter") {
        $type = "Message"
        $typeNamespace = "MSGraph.Exchange.Mail"
        $nounPreFix = "MgaMail"
        $parameterName = "InputObject"
    } elseif ($Object.psobject.TypeNames[0] -like "MSGraph.Exchange.Category.CategoryParameter") {
        $type = "Category"
        $typeNamespace = "MSGraph.Exchange.Category"
        $nounPreFix = "MgaExch"
        if ($Object.Id) { $parameterName = "Id" } else { $parameterName = "Name" }
    } else {
        $msg = "Object '$($Object)' is not valid. Must be one of: 'MSGraph.Exchange.Mail.FolderParameter', 'MSGraph.Exchange.Mail.MessageParameter', 'MSGraph.Exchange.Category.CategoryParameter'."
        Stop-PSFFunction -Message $msg -Tag "InputValidation" -FunctionName $FunctionName -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
    }
    Write-PSFMessage -Level Debug -Message "Object '$($Object)' is qualified as a $($type)" -Tag "InputValidation" -FunctionName $FunctionName

    # Resolve the object
    if ($Object.Id -and (Test-MgaMailObjectId -Id $Object.Id -Type $type -FunctionName $FunctionName)) {
        Write-PSFMessage -Level Debug -Message "Going to resolve '$($Object)' with Id" -Tag "InputValidation" -FunctionName $FunctionName
        $invokeParam.Add($parameterName, $Object.Id)
        $output = .("Get-" + $nounPreFix + $type) @invokeParam
    } elseif ($Object.Name -and (-not $NoNameResolving)) {
        Write-PSFMessage -Level Debug -Message "Going to resolve '$($Object)' with name" -Tag "InputValidation" -FunctionName $FunctionName
        $invokeParam.Add($parameterName, $Object.Name)
        $invokeParam.Add("ErrorAction", "Stop")
        $output = .("Get-" + $nounPreFix + $type) @invokeParam
    } else {
        # not valid, end function without output
        Write-PSFMessage -Level Warning -Message "The specified input string seams not to be a valid Id. Skipping object '$($Object)'" -Tag "InputValidation" -FunctionName $FunctionName
        return
    }

    # output the result
    if ($output) {
        New-Object -TypeName "$($typeNamespace).$($type)Parameter" -ArgumentList $output
    }
}

function Resolve-UserInMailObject {
    <#
    .SYNOPSIS
        Resolves the user from a mail or folder parameter class
 
    .DESCRIPTION
        Resolves the user a mail or folder parameter class and compares against the specified user.
        If user in object is different, from the specified user, the user from the object is put out.
        Helper function used for internal commands.
 
    .PARAMETER Object
        The mail or folder object
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER ShowWarning
        If specified, there will be no warning output on the console.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> Resolve-UserInMailObject -Object $MailFolder -User $User -Function $MyInvocation.MyCommand
 
        Resolves the user from a mail or folder parameter class.
    #>

    [OutputType()]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    param (
        $Object,

        [string]
        $User,

        [switch]
        $ShowWarning,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    # check input object type
    [bool]$failed = $false
    switch ($Object.psobject.TypeNames[0]) {
        "MSGraph.Exchange.Mail.FolderParameter" {
            $namespace = "MSGraph.Exchange.Mail"
            $Type = "Folder"
        }

        "MSGraph.Exchange.Mail.MessageParameter" {
            $namespace = "MSGraph.Exchange.Mail"
            $Type = "Message"
        }

        "MSGraph.Exchange.Category.CategoryParameter" {
            $namespace = "MSGraph.Exchange.Category"
            $Type = "Category"
        }

        "MSGraph.Exchange.MailboxSetting.MailboxSettingParameter" {
            $namespace = "MSGraph.Exchange.MailboxSetting"
            $Type = "MailboxSettings"
        }

        Default { $failed = $true }
    }
    if($failed) {
        $msg = "Object '$($Object)' is not valid. Must be one of: 'MSGraph.Exchange.*.*Parameter' object types. Developers mistake!"
        Stop-PSFFunction -Message $msg -Tag "InputValidation" -FunctionName $FunctionName -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
    }
    Write-PSFMessage -Level Debug -Message "Object '$($Object)' is qualified as a $($Type)" -Tag "InputValidation" -FunctionName $FunctionName

    if ($ShowWarning) {
        $level = @{ Level = "Warning" }
    } else {
        $level = @{ Level = "Verbose" }
    }

    $output = ""
    # Resolve the object
    if ($User -and ($Object.TypeName -like "$($namespace).$($Type)") -and ($User -notlike $Object.InputObject.User)) {
        Write-PSFMessage @Level -Message "Individual user specified! User from $($Type)Object ($($Object.InputObject.User)) will take precedence on specified user ($($User))!" -Tag "InputValidation" -FunctionName $FunctionName
        $output = $Object.InputObject.User
    } elseif ((-not $User) -and ($Object.TypeName -like "$($namespace).$($Type)")) {
        $output = $Object.InputObject.User
    }

    # output the result
    $output
}

function Test-MgaMailObjectId {
    <#
    .SYNOPSIS
        Test for valid object ID length on folders or message objects
 
    .DESCRIPTION
        Validates the length of an Id for objects in Exchange Online
        Helper function used for internal commands.
 
    .PARAMETER Id
        The Id to test.
 
    .PARAMETER Type
        The expected type of the object
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .EXAMPLE
        PS C:\> Test-MgaMailObjectId -Id $Id -Scope Folder
 
        Test if the specified $Id is a folder
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [validateset("Folder", "Message", "Category")]
        [string]
        $Type,

        [String]
        $FunctionName = $MyInvocation.MyCommand
    )

    begin {
        $status = $false
        [guid]$guidId = [guid]::Empty
    }

    process {
        $Token = Resolve-Token -Token $Token -FunctionName $MyInvocation.MyCommand

        Write-PSFMessage -Level Debug -Message "Validating Id '$($Id)' for $($Type) length" -Tag "ValidateObjectId" -FunctionName $FunctionName
        switch ($Type) {
            "Folder" { if ($Id.Length -eq 120 -or $Id.Length -eq 104) { $status = $true } }
            "Message" { if ($Id.Length -eq 152 -or $Id.Length -eq 136) { $status = $true } }
            "Category" { if ( [guid]::TryParse($Id, [ref]$guidId) ) { $status = $true } }
        }
    }

    end {
        if ($status) { Write-PSFMessage -Level Debug -Message "Id has appropriate length ($($Id.Length)) to be a $($Type)." -Tag "ValidateObjectId" -FunctionName $FunctionName }
        $status
    }
}

function New-JsonAttachmentObject {
    <#
    .SYNOPSIS
        Creates a json attachment object for use in Microsoft Graph REST api
 
    .DESCRIPTION
        Creates a json attachment object for use in Microsoft Graph REST api
        Helper function used for internal commands.
 
    .PARAMETER Name
        The name of attachment.
 
    .PARAMETER Size
        The size in bytes of the attachment.
 
    .PARAMETER IsInline
        Set to true if this is an inline attachment.
 
    .PARAMETER LastModifiedDateTime
        The date and time when the attachment was last modified.
        The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time.
        For example, midnight UTC on Jan 1, 2014 would look like this: '2014-01-01T00:00:00Z'
 
    .PARAMETER ContentType
        The content type of the attachment.
 
    .PARAMETER contentBytes
        The base64-encoded contents of the file.
 
    .PARAMETER contentLocation
        The Uniform Resource Identifier (URI) that corresponds to the location of the content of the attachment.
 
    .PARAMETER Item
        The attached message or event. Navigation property.
 
    .PARAMETER IsFolder
        Property indicates, wether the object is a folder or not.
 
    .PARAMETER Permission
        The stated permission on the reference attachment.
 
    .PARAMETER PreviewUrl
        The url the preview the reference attachment.
 
    .PARAMETER ProviderType
        Specifies what type of reference is it.
 
    .PARAMETER SourceUrl
        The Url where the reference attachment points to.
 
    .PARAMETER ThumbnailUrl
        The Url of the thumbnail for the reference attachment.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/resources/attachment?view=graph-rest-1.0
 
        https://docs.microsoft.com/en-us/graph/api/resources/fileattachment?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/resources/itemattachment?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/resources/referenceattachment?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> New-JsonAttachmentObject
 
        Creates a json attachment object for use in Microsoft Graph REST api
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low', DefaultParameterSetName = 'FileAttachment')]
    [OutputType([String])]
    param (
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $Name,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [int32]
        $Size,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [bool]
        $IsInline,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $LastModifiedDateTime,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $ContentType,

        [Parameter(ParameterSetName = 'FileAttachment')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $contentBytes,

        [Parameter(ParameterSetName = 'FileAttachment')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $contentLocation,

        [Parameter(ParameterSetName = 'ItemAttachment')]
        [psobject]
        $Item,

        [Parameter(ParameterSetName = 'ReferenceAttachment')]
        [String]
        $SourceUrl,

        [Parameter(ParameterSetName = 'ReferenceAttachment')]
        [String]
        $ProviderType,

        [Parameter(ParameterSetName = 'ReferenceAttachment')]
        [String]
        $ThumbnailUrl,

        [Parameter(ParameterSetName = 'ReferenceAttachment')]
        [String]
        $PreviewUrl,

        [Parameter(ParameterSetName = 'ReferenceAttachment')]
        [String]
        $Permission,

        [Parameter(ParameterSetName = 'ReferenceAttachment')]
        [bool]
        $IsFolder,

        [String]
        $FunctionName
    )
    begin {
    }

    process {
        Write-PSFMessage -Level Debug -Message "Create attachment JSON object" -Tag "ParameterSetHandling"

        #region variable definition
        $boundParameters = @()
        $bodyHash = [ordered]@{}
        $variableNames = @("Name", "Size", "IsInline", "LastModifiedDateTime", "ContentType")
        switch ($PSCmdlet.ParameterSetName) {
            'FileAttachment' { $variableNames = $variableNames + @("contentBytes", "contentLocation") }
            'ItemAttachment' { $variableNames = $variableNames + @("item") }
            'ReferenceAttachment' { $variableNames = $variableNames + @("SourceUrl", "ProviderType", "ThumbnailUrl", "PreviewUrl", "Permission", "IsFolder") }
        }
        #endregion variable definition

        #region Parsing string and boolean parameters to json data parts
        Write-PSFMessage -Level VeryVerbose -Message "Parsing parameters to json data parts ($([string]::Join(", ", $variableNames)))" -Tag "ParameterParsing" -FunctionName $FunctionName

        $bodyHash.Add("@odata.type", """#microsoft.graph.$($PSCmdlet.ParameterSetName)""")

        foreach ($variableName in $variableNames) {
            if (Test-PSFParameterBinding -ParameterName $variableName) {
                $boundParameters = $boundParameters + $variableName
                Write-PSFMessage -Level Debug -Message "Parsing parameter $($variableName)" -Tag "ParameterParsing"
                $bodyHash.Add($variableName, ((Get-Variable $variableName -Scope 0).Value | ConvertTo-Json))
            }
        }
        #endregion Parsing string and boolean parameters to json data parts

        # Put parameters (JSON Parts) into a valid JSON-object together and output the result
        $bodyJSON = Merge-HashToJson $bodyHash
        $bodyJSON
    }

    end {
    }
}

function New-MgaAttachmentObject {
    <#
    .SYNOPSIS
        Create new Attachment object
 
    .DESCRIPTION
        Create new Attachment object
        Helper function used for internal commands.
 
    .PARAMETER RestData
        The RestData object containing the data for the new message object.
 
    .PARAMETER ParentObject
        The ParentObject object where the attachment came from.
 
    .PARAMETER ApiVersion
        The version used for queries in Microsoft Graph connection
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER User
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER Token
        The access token to use to connect.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> New-MgaAttachmentObject -RestData $output
 
        Create a MSGraph.Exchange.Attachment.* object from data in variable $output
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    param (
        $RestData,

        $ParentObject,

        $ApiVersion,

        [Int64]
        $ResultSize,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [String]
        $FunctionName
    )

    $outputHash = [ordered]@{
        Id                   = $RestData.Id
        Name                 = $RestData.Name
        AttachmentType       = [MSGraph.Exchange.Attachment.AttachmentTypes]$RestData.'@odata.type'.split(".")[($RestData.'@odata.type'.split(".").count - 1)]
        ContentType          = $RestData.ContentType
        IsInline             = $RestData.isInline
        LastModifiedDateTime = $RestData.LastModifiedDateTime
        Size                 = $RestData.Size
        User                 = $RestData.user
        ParentObject         = $ParentObject
        BaseObject           = $RestData
    }

    switch ($RestData.'@odata.type') {
        '#microsoft.graph.itemAttachment' {
            $invokeParam = @{
                "Field"        = "messages/$($ParentObject.Id)/attachments/$($RestData.id)/?`$expand=microsoft.graph.itemattachment/item"
                "Token"        = $Token
                "User"         = $User
                "ResultSize"   = $ResultSize
                "ApiVersion"   = $ApiVersion
                "FunctionName" = $FunctionName
            }
            $itemData = Invoke-MgaRestMethodGet @invokeParam

            $outputHash.BaseObject = $itemData
            $outputHash.Id = $itemData.id
            $outputHash.Add("Item", $itemData.Item)

            New-Object -TypeName MSGraph.Exchange.Attachment.ItemAttachment -Property $outputHash
        }

        '#microsoft.graph.referenceAttachment' {
            $outputHash.Add("SourceUrl", [uri]$RestData.SourceUrl)
            $outputHash.Add("ProviderType", [MSGraph.Exchange.Attachment.ReferenceAttachmentProvider]$RestData.ProviderType)
            $outputHash.Add("ThumbnailUrl", [uri]$RestData.ThumbnailUrl)
            $outputHash.Add("PreviewUrl", [uri]$RestData.PreviewUrl)
            $outputHash.Add("Permission", [MSGraph.Exchange.Attachment.referenceAttachmentPermission]$RestData.Permission)
            $outputHash.Add("IsFolder", [bool]::Parse($RestData.IsFolder))

            New-Object -TypeName MSGraph.Exchange.Attachment.ReferenceAttachment -Property $outputHash
        }

        '#microsoft.graph.fileAttachment' {
            $outputHash.Add("ContentId", $RestData.ContentId)
            $outputHash.Add("ContentLocation", $RestData.ContentLocation)
            $outputHash.Add("ContentBytes", [system.convert]::FromBase64String($RestData.contentBytes))

            New-Object -TypeName MSGraph.Exchange.Attachment.FileAttachment -Property $outputHash
        }

        Default {
            New-Object -TypeName MSGraph.Exchange.Attachment.Attachment -Property $outputHash
        }
    }

}

function New-JsonMailObject {
    <#
    .SYNOPSIS
        Creates a json message object for use in Microsoft Graph REST api
 
    .DESCRIPTION
        Creates a json message object for use in Microsoft Graph REST api
        Helper function used for internal commands.
 
    .PARAMETER Subject
        The subject of the new message.
 
    .PARAMETER Sender
        The account that is actually used to generate the message.
        (Updatable only when sending a message from a shared mailbox or sending a message as a delegate.
        In any case, the value must correspond to the actual mailbox used.)
 
    .PARAMETER From
        The mailbox owner and sender of the message.
        Must correspond to the actual mailbox used.
 
    .PARAMETER ToRecipients
        The To recipients for the message.
 
    .PARAMETER CCRecipients
        The Cc recipients for the message.
 
    .PARAMETER BCCRecipients
        The Bcc recipients for the message.
 
    .PARAMETER ReplyTo
        The email addresses to use when replying.
 
    .PARAMETER Body
        The body of the message.
 
    .PARAMETER Categories
        The categories associated with the message.
 
    .PARAMETER Importance
        The importance of the message.
        The possible values are: Low, Normal, High.
 
    .PARAMETER InferenceClassification
        The classification of the message for the user, based on inferred relevance or importance, or on an explicit override.
        The possible values are: focused or other.
 
    .PARAMETER InternetMessageId
        The message ID in the format specified by RFC2822.
 
    .PARAMETER IsDeliveryReceiptRequested
        Indicates whether a delivery receipt is requested for the message.
 
    .PARAMETER IsReadReceiptRequested
        Indicates whether a read receipt is requested for the message.
 
    .PARAMETER IsRead
        Indicates whether the message has been read.
 
    .PARAMETER Comment
        The comment in a body of a forwarded message.
        Only used when forwarding messages.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/resources/message?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> New-JsonMailObject
 
        Creates a json message object for use in Microsoft Graph REST api
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [OutputType([String])]
    param (
        [bool]
        $IsRead,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $Subject,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $Sender,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $From,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $ToRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $CCRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $BCCRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $ReplyTo,

        [String]
        $Body,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [String[]]
        $Categories,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [ValidateSet($null, "", "Low", "Normal", "High")]
        [String]
        $Importance,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [ValidateSet($null, "", "focused", "other")]
        [String]
        $InferenceClassification,

        [String]
        $InternetMessageId,

        [bool]
        $IsDeliveryReceiptRequested,

        [bool]
        $IsReadReceiptRequested,

        [String]
        $Comment,

        [String]
        $FunctionName
    )
    begin {
        #region variable definition
        $boundParameters = @()
        $mailAddressNames = @("sender", "from", "toRecipients", "ccRecipients", "bccRecipients", "replyTo")
        #endregion variable definition

        # parsing mailAddress parameter strings to mailaddress objects (if not empty)
        foreach ($Name in $mailAddressNames) {
            if (Test-PSFParameterBinding -ParameterName $name) {
                New-Variable -Name "$($name)Addresses" -Force -Scope 0
                if ((Get-Variable -Name $Name -Scope 0).Value) {
                    try {
                        $mailAddress = (Get-Variable -Name $Name -Scope 0).Value | ForEach-Object { [mailaddress]$_ } -ErrorAction Stop -ErrorVariable parseError
                        Set-Variable -Name "$($name)Addresses" -Value $mailAddress
                        Remove-Variable mailaddress -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction Ignore
                    } catch {
                        Stop-PSFFunction -Message "Unable to parse $($name) to a mailaddress. String should be 'name@domain.topleveldomain' or 'displayname name@domain.topleveldomain'. Error: $($parseError[0].Exception.Message)" -Tag "ParameterParsing" -Category InvalidData -EnableException $true -Exception $parseError[0].Exception -FunctionName $FunctionName
                    }
                }
            }
        }

    }

    process {
        $bodyHash = @{}
        Write-PSFMessage -Level Debug -Message "Create message JSON object" -Tag "ParameterSetHandling"

        #region Parsing string and boolean parameters to json data parts
        $names = @("Comment", "IsRead", "Subject", "Categories", "Importance", "InferenceClassification", "InternetMessageId", "IsDeliveryReceiptRequested", "IsReadReceiptRequested")
        Write-PSFMessage -Level VeryVerbose -Message "Parsing string and boolean parameters to json data parts ($([string]::Join(", ", $names)))" -Tag "ParameterParsing"
        foreach ($name in $names) {
            if (Test-PSFParameterBinding -ParameterName $name) {
                $boundParameters = $boundParameters + $name
                Write-PSFMessage -Level Debug -Message "Parsing text parameter $($name)" -Tag "ParameterParsing"
                $bodyHash.Add($name, ((Get-Variable $name -Scope 0).Value | ConvertTo-Json))
            }
        }

        if ($Body) {
            $bodyHash.Add("Body", ([MSGraph.Exchange.Mail.MessageBody]$Body | ConvertTo-Json))
        }
        #endregion Parsing string and boolean parameters to json data parts

        #region Parsing mailaddress parameters to json data parts
        Write-PSFMessage -Level VeryVerbose -Message "Parsing mailaddress parameters to json data parts ($([string]::Join(", ", $mailAddressNames)))" -Tag "ParameterParsing"
        foreach ($name in $mailAddressNames) {
            if (Test-PSFParameterBinding -ParameterName $name) {
                $boundParameters = $boundParameters + $name
                Write-PSFMessage -Level Debug -Message "Parsing mailaddress parameter $($name)" -Tag "ParameterParsing"
                $addresses = (Get-Variable -Name "$($name)Addresses" -Scope 0).Value
                if ($addresses) {
                    # build valid mail address object, if address is specified
                    [array]$addresses = foreach ($item in $addresses) {
                        [PSCustomObject]@{
                            emailAddress = [PSCustomObject]@{
                                address = $item.Address
                                name    = $item.DisplayName
                            }
                        }
                    }
                } else {
                    # place an empty mail address object in, if no address is specified (this will clear the field in the message)
                    [array]$addresses = [PSCustomObject]@{
                        emailAddress = [PSCustomObject]@{
                            address = ""
                            name    = ""
                        }
                    }
                }

                if ($name -in @("toRecipients", "ccRecipients", "bccRecipients", "replyTo")) {
                    # these kind of objects need to be an JSON array
                    if ($addresses.Count -eq 1) {
                        # hardly format JSON object as an array, because ConvertTo-JSON will output a single object-json-string on an array with count 1 (PSVersion 5.1.17134.407 | PSVersion 6.1.1)
                        $bodyHash.Add($name, ("[" + ($addresses | ConvertTo-Json) + "]") )
                    } else {
                        $bodyHash.Add($name, ($addresses | ConvertTo-Json) )
                    }
                } else {
                    $bodyHash.Add($name, ($addresses | ConvertTo-Json) )
                }
            }
        }
        #endregion Parsing mailaddress parameters to json data parts

        # Put parameters (JSON Parts) into a valid JSON-object together and output the result
        $bodyJSON = Merge-HashToJson $bodyHash
        $bodyJSON
    }

    end {
    }
}

function New-MgaMailFolderObject {
    <#
    .SYNOPSIS
        Create new FolderObject
 
    .DESCRIPTION
        Create new FolderObject
        Helper function used for internal commands.
 
    .PARAMETER RestData
        The RestData object containing the data for the new message object.
 
    .PARAMETER Level
        The hierarchy level of the folder.
        1 means the folder is a root folder.
 
    .PARAMETER ParentFolder
        If known/ existing, the parent folder object of the folder object to create.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> New-MgaMailFolderObject -RestData $output -Level $Level -ParentFolder $ParentFolder -FunctionName $MyInvocation.MyCommand
 
        Create a MSGraph.Exchange.Mail.Folder object from data in variable $output
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [OutputType([MSGraph.Exchange.Mail.Folder])]
    [CmdletBinding()]
    param (
        $RestData,

        [MSGraph.Exchange.Mail.FolderParameter]
        $ParentFolder,

        [int]
        $Level,

        [String]
        $FunctionName
    )

    if ((-not $Level) -and $ParentFolder) {
        $Level = $ParentFolder.InputObject.HierarchyLevel + 1
    } elseif ((-not $Level) -and (-not $ParentFolder)) {
        $Level = 1
    }

    $hash = @{
        Id               = $RestData.Id
        DisplayName      = $RestData.DisplayName
        ParentFolderId   = $RestData.ParentFolderId
        ChildFolderCount = $RestData.ChildFolderCount
        UnreadItemCount  = $RestData.UnreadItemCount
        TotalItemCount   = $RestData.TotalItemCount
        User             = $RestData.User
        HierarchyLevel   = $Level
    }

    if ($ParentFolder) { $hash.Add("ParentFolder", $ParentFolder.InputObject) }

    $OutputObject = New-Object -TypeName MSGraph.Exchange.Mail.Folder -Property $hash

    $OutputObject
}

function New-MgaMailMessageObject {
    <#
    .SYNOPSIS
        Create new MessageObject
 
    .DESCRIPTION
        Create new MessageObject
        Helper function used for internal commands.
 
    .PARAMETER RestData
        The RestData object containing the data for the new message object.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> New-MgaMailMessageObject -RestData $output
 
        Create a MSGraph.Exchange.Mail.Message object from data in variable $output
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    [CmdletBinding()]
    param (
        $RestData,

        [String]
        $FunctionName
    )

    #region Handle dates
    if ($RestData.createdDateTime.psobject.TypeNames[0] -like "System.DateTime") {
        $createdDateTime = $RestData.createdDateTime
    } else {
        $createdDateTime = [datetime]::Parse($RestData.createdDateTime)
    }

    if ($RestData.lastModifiedDateTime.psobject.TypeNames[0] -like "System.DateTime") {
        $lastModifiedDateTime = $RestData.lastModifiedDateTime
    } else {
        $lastModifiedDateTime = [datetime]::Parse($RestData.lastModifiedDateTime)
    }

    if ($RestData.receivedDateTime.psobject.TypeNames[0] -like "System.DateTime") {
        $receivedDateTime = $RestData.receivedDateTime
    } else {
        if($RestData.receivedDateTime) {
            $receivedDateTime = [datetime]::Parse($RestData.receivedDateTime)
        } else {
            $receivedDateTime = ""
        }
    }

    if ($RestData.sentDateTime.psobject.TypeNames[0] -like "System.DateTime") {
        $sentDateTime = $RestData.sentDateTime
    } else {
        if($RestData.sentDateTime) {
            $sentDateTime = [datetime]::Parse($RestData.sentDateTime)
        } else {
            $sentDateTime = ""
        }
    }

    #endregion Handle dates

    $hash = [ordered]@{
        BaseObject                 = $RestData
        Subject                    = $RestData.subject
        Body                       = $RestData.body
        BodyPreview                = $RestData.bodyPreview
        Categories                 = $RestData.categories
        ChangeKey                  = $RestData.changeKey
        ConversationId             = $RestData.conversationId
        CreatedDateTime            = $createdDateTime
        Flag                       = $RestData.flag.flagStatus
        HasAttachments             = $RestData.hasAttachments
        Id                         = $RestData.id
        Importance                 = $RestData.importance
        InferenceClassification    = $RestData.inferenceClassification
        InternetMessageId          = $RestData.internetMessageId
        IsDeliveryReceiptRequested = $RestData.isDeliveryReceiptRequested
        IsDraft                    = $RestData.isDraft
        IsRead                     = $RestData.isRead
        isReadReceiptRequested     = $RestData.isReadReceiptRequested
        lastModifiedDateTime       = $lastModifiedDateTime
        MeetingMessageType         = $RestData.meetingMessageType
        ParentFolderId             = $RestData.parentFolderId
        WebLink                    = $RestData.webLink
        User                       = $RestData.User
    }
    if ($RestData.receivedDateTime) { $hash.Add("ReceivedDateTime", $receivedDateTime) }
    if ($RestData.sentDateTime) { $hash.Add("SentDateTime", $sentDateTime) }
    if ($RestData.from.emailAddress) {
        if ($RestData.from.emailAddress.name -like $RestData.from.emailAddress.address) {
            # if emailaddress is same in address and in name field, only use address field
            $from = $RestData.from.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue
        } else {
            $from = $RestData.from.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue
        }
        $hash.Add("from", $from)
    }
    if ($RestData.Sender.emailAddress) {
        if ($RestData.Sender.emailAddress.name -like $RestData.Sender.emailAddress.address) {
            # if emailaddress is same in address and in name field, only use address field
            $senderaddress = $RestData.Sender.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue
        } else {
            $senderaddress = $RestData.Sender.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue
        }
        $hash.Add("Sender", $senderaddress)
    }
    if ($RestData.bccRecipients.emailAddress) {
        if ($RestData.bccRecipients.emailAddress.name -like $RestData.bccRecipients.emailAddress.address) {
            # if emailaddress is same in address and in name field, only use address field
            [array]$bccRecipients = $RestData.bccRecipients.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue
        } else {
            [array]$bccRecipients = $RestData.bccRecipients.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue
        }
        $hash.Add("bccRecipients", [array]$bccRecipients)
    }
    if ($RestData.ccRecipients.emailAddress) {
        if ($RestData.ccRecipients.emailAddress.name -like $RestData.ccRecipients.emailAddress.address) {
            # if emailaddress is same in address and in name field, only use address field
            [array]$ccRecipients = $RestData.ccRecipients.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue
        } else {
            [array]$ccRecipients = $RestData.ccRecipients.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue
        }
        $hash.Add("ccRecipients", [array]$ccRecipients)
    }
    if ($RestData.replyTo.emailAddress) {
        if ($RestData.replyTo.emailAddress.name -like $RestData.replyTo.emailAddress.address) {
            # if emailaddress is same in address and in name field, only use address field
            [array]$replyTo = $RestData.replyTo.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue
        } else {
            [array]$replyTo = $RestData.replyTo.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue
        }
        $hash.Add("replyTo", [array]$replyTo)
    }
    if ($RestData.toRecipients.emailAddress) {
        if ($RestData.toRecipients.emailAddress.name -like $RestData.toRecipients.emailAddress.address) {
            # if emailaddress is same in address and in name field, only use address field
            [array]$toRecipients = $RestData.toRecipients.emailAddress | ForEach-Object { [mailaddress]$_.address } -ErrorAction Continue
        } else {
            [array]$toRecipients = $RestData.toRecipients.emailAddress | ForEach-Object { [mailaddress]"$($_.name) $($_.address)" } -ErrorAction Continue
        }
        $hash.Add("toRecipients", [array]$toRecipients)
    }

    $messageOutputObject = New-Object -TypeName MSGraph.Exchange.Mail.Message -Property $hash
    $messageOutputObject
}

function New-JsonAutomaticRepliesSettingFraction {
    <#
    .SYNOPSIS
        Creates a json object from AutomaticRepliesSetting object
 
    .DESCRIPTION
        Creates a json object from AutomaticRepliesSetting object used for Microsoft Graph REST api
        Helper function used for internal commands.
 
    .PARAMETER AutomaticRepliesSetting
        The object to convert to json
 
    .EXAMPLE
        PS C:\> New-JsonAutomaticRepliesSettingFraction -AutomaticRepliesSetting $automaticRepliesSetting
 
        Creates a json object from AutomaticRepliesSetting object used for Microsoft Graph REST api
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [OutputType([String])]
    param (
        [MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting]
        $AutomaticRepliesSetting
    )

    $automaticRepliesSettingHash = [ordered]@{
        "status"                 = $AutomaticRepliesSetting.Status.ToString()
        "externalAudience"       = $AutomaticRepliesSetting.ExternalAudience.ToString()
        "internalReplyMessage"   = $AutomaticRepliesSetting.InternalReplyMessage
        "externalReplyMessage"   = $AutomaticRepliesSetting.ExternalReplyMessage
        "scheduledStartDateTime" = [ordered]@{
            #"dateTime" = ($AutomaticRepliesSetting.ScheduledStartDateTimeUTC.DateTime | Get-Date -Format s)
            "dateTime" = $AutomaticRepliesSetting.ScheduledStartDateTimeUTC.DateTime.ToString("s") # "s" means sortable date: 2000-01-01T01:01:01(.010001)
            "timeZone" = $AutomaticRepliesSetting.ScheduledStartDateTimeUTC.TimeZone
        }
        "scheduledEndDateTime"   = [ordered]@{
            #"dateTime" = ($AutomaticRepliesSetting.ScheduledEndDateTimeUTC.DateTime | Get-Date -Format s)
            "dateTime" = $AutomaticRepliesSetting.ScheduledEndDateTimeUTC.DateTime.ToString("s") # "s" means sortable date: 2000-01-01T01:01:01(.010001)
            "timeZone" = $AutomaticRepliesSetting.ScheduledEndDateTimeUTC.TimeZone
        }
    }
    $automaticRepliesSettingObject = New-Object psobject -Property $automaticRepliesSettingHash
    $automaticRepliesSettingJSON = ConvertTo-Json -InputObject $automaticRepliesSettingObject
    $automaticRepliesSettingJSON
}


function New-JsonLanguageSettingFraction {
    <#
    .SYNOPSIS
        Creates a json object from LocaleInfoSetting (LanguageSetting) object
 
    .DESCRIPTION
        Creates a json object from LocaleInfoSetting (LanguageSetting) object used for Microsoft Graph REST api
        Helper function used for internal commands.
 
    .PARAMETER LanguageSetting
        The object to convert to json
 
    .EXAMPLE
        PS C:\> New-JsonLanguageSettingFraction -LanguageSetting $languageSetting
 
        Creates a json object from LanguageSetting object used for Microsoft Graph REST api
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [OutputType([String])]
    param (
        [MSGraph.Exchange.MailboxSetting.LocaleInfoSetting]
        $LanguageSetting
    )

    $languageSettingHash = [ordered]@{
        "locale"      = $LanguageSetting.Locale.ToString()
        #"displayName" = $LanguageSetting.DisplayName # causes errors on rest patch call
    }
    $languageSettingObject = New-Object psobject -Property $languageSettingHash
    $languageSettingJSON = ConvertTo-Json -InputObject $languageSettingObject
    $languageSettingJSON
}


function New-JsonMailboxSettingObject {
    <#
    .SYNOPSIS
        Creates a json mailsettings object for use in Microsoft Graph REST api
 
    .DESCRIPTION
        Creates a json mailsettings object for use in Microsoft Graph REST api
        Helper function used for internal commands.
 
    .PARAMETER SettingObject
        The object to be converted into JSON format containing the data for the new message object.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
        (Just used for logging reasons)
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/user-update-mailboxsettings?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> New-JsonMailboxSettingObject -SettingObject $settingObject -User $user -FunctionName $MyInvocation.MyCommand
 
        Creates a json MailboxSetting object for use in Microsoft Graph REST api
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [OutputType([String])]
    param (
        [MSGraph.Exchange.MailboxSetting.MailboxSettingParameter]
        $SettingObject,

        [string]
        $User,

        [String]
        $FunctionName
    )
    begin {
    }

    process {
        Write-PSFMessage -Level Debug -Message "Working on '$($SettingObject)' to create mailboxSetting JSON object" -Tag "ParameterSetHandling"
        #region variable definition
        $bodyHash = [ordered]@{}

        #endregion variable definition

        #region Parsing input to json data parts
        # set field @odata.context - required
        if ($SettingObject.InputObject.BaseObject.'@odata.context') {
            $context = $SettingObject.InputObject.BaseObject.'@odata.context'
            if ($context -match '\/mailboxSettings\/\w*$') { $context = $context.Replace($Matches.Values, "/mailboxsettings") }
            Remove-Variable -Name Matches -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore
        } else {
            $apiConnection = Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'
            $apiVersion = Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'
            $resolvedUser = Resolve-UserString -User $User -ContextData
            $context = "$($apiConnection)/$($apiVersion)/`$metadata#$($resolvedUser)/mailboxsettings"
            Remove-Variable -Name apiConnection, apiVersion -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore
        }
        $bodyHash.Add('@odata.context', """$context""")

        # depending on type of object
        switch ($SettingObject.TypeName) {
            'MSGraph.Exchange.MailboxSetting.MailboxSettings' {
                # set archive folder
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting archive folder to '$($SettingObject.InputObject.ArchiveFolder)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $bodyHash.Add('archiveFolder', ($SettingObject.InputObject.ArchiveFolder.Id | ConvertTo-Json))

                # set time zone
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting timezone to '$($SettingObject.InputObject.TimeZone.Id)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $bodyHash.Add('timeZone', ($SettingObject.InputObject.TimeZone.Id | ConvertTo-Json))
                #$bodyHash.Add('timeZone', ('"' + "W. Europe Standard Time" + '"'))

                # set auto reply
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting autoreply to '$($SettingObject.InputObject.automaticRepliesSetting.Status)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $automaticRepliesSettingJSON = New-JsonAutomaticRepliesSettingFraction -AutomaticRepliesSetting $SettingObject.InputObject.automaticRepliesSetting
                $bodyHash.Add('automaticRepliesSetting', $automaticRepliesSettingJSON)

                # set language
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting language to '$($SettingObject.InputObject.Language)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $languageSettingJSON = New-JsonLanguageSettingFraction -LanguageSetting $SettingObject.InputObject.Language
                $bodyHash.Add('language', $languageSettingJSON)

                # set working hours
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting workingHours to '$($SettingObject.InputObject.WorkingHours)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $workingHoursSettingJSON = New-JsonWorkingHoursSettingFraction -WorkingHoursSetting $SettingObject.InputObject.WorkingHours
                $bodyHash.Add('workingHours', $workingHoursSettingJSON)
            }

            'MSGraph.Exchange.Mail.Folder' {
                # set archive folder
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting archive folder to '$($SettingObject.InputObject)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $bodyHash.Add('archiveFolder', ($SettingObject.InputObject.Id | ConvertTo-Json))
            }

            'System.TimeZoneInfo' {
                # set time zone
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting timezone to '$($SettingObject.InputObject.Id)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $bodyHash.Add('timeZone', ($SettingObject.InputObject.Id | ConvertTo-Json))
            }

            'MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting' {
                # set auto reply
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting autoreply to '$($SettingObject.InputObject.Status)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $automaticRepliesSettingJSON = New-JsonAutomaticRepliesSettingFraction -AutomaticRepliesSetting $SettingObject.InputObject
                $bodyHash.Add('automaticRepliesSetting', $automaticRepliesSettingJSON)
            }

            'MSGraph.Exchange.MailboxSetting.LocaleInfoSetting' {
                # set language
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting language to '$($SettingObject.InputObject)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $languageSettingJSON = New-JsonLanguageSettingFraction -LanguageSetting $SettingObject.InputObject
                $bodyHash.Add('language', $languageSettingJSON)
            }

            'MSGraph.Exchange.MailboxSetting.WorkingHoursSetting' {
                # set working hours
                Write-PSFMessage -Level VeryVerbose -Message "Prepare setting workingHours to '$($SettingObject.InputObject)'" -Tag "CreateJSON" -FunctionName $FunctionName
                $workingHoursSettingJSON = New-JsonWorkingHoursSettingFraction -WorkingHoursSetting $SettingObject.InputObject
                $bodyHash.Add('workingHours', $workingHoursSettingJSON)
            }

            Default { Stop-PSFFunction -Message "Unhandled type ($($SettingObject.TypeName)) of SettingObject. Developer mistake!" -EnableException $true -Category InvalidType -FunctionName $MyInvocation.MyCommand }
        }
        #endregion Parsing input to json data parts

        # Put parameters (JSON Parts) into a valid JSON-object and output the result
        $bodyJSON = Merge-HashToJSON $bodyHash
        $bodyJSON
    }

    end {
    }
}

function New-JsonWorkingHoursSettingFraction {
    <#
    .SYNOPSIS
        Creates a json object from WorkingHoursSetting object
 
    .DESCRIPTION
        Creates a json object from WorkingHoursSetting object used for Microsoft Graph REST api
        Helper function used for internal commands.
 
    .PARAMETER WorkingHoursSetting
        The object to convert to json
 
    .EXAMPLE
        PS C:\> New-JsonWorkingHoursSettingFraction -WorkingHoursSetting $workingHoursSetting
 
        Creates a json object from WorkingHoursSetting object used for Microsoft Graph REST api
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(SupportsShouldProcess = $false, ConfirmImpact = 'Low')]
    [OutputType([String])]
    param (
        [MSGraph.Exchange.MailboxSetting.WorkingHoursSetting]
        $WorkingHoursSetting
    )

    $workingHoursSettingHash = [ordered]@{
        "daysOfWeek" = [array]$WorkingHoursSetting.DaysOfWeek.ForEach( {$_.ToString()} )
        "startTime"  = $WorkingHoursSetting.StartTime.ToString("HH:mm:ss.fffffff")
        "endTime"    = $WorkingHoursSetting.EndTime.ToString("HH:mm:ss.fffffff")
        "timeZone"   = @{
            "name" = $WorkingHoursSetting.TimeZone.ToString()
        }
    }
    $workingHoursSettingObject = New-Object psobject -Property $workingHoursSettingHash
    $workingHoursSettingJSON = ConvertTo-Json -InputObject $workingHoursSettingObject
    $workingHoursSettingJSON
}


function New-MgaMailboxSettingObject {
    <#
    .SYNOPSIS
        Create new mailboxSettings object
 
    .DESCRIPTION
        Create new mailboxSettings object
        Helper function used for internal commands.
 
    .PARAMETER RestData
        The RestData object containing the data for the new message object.
 
    .PARAMETER Type
        The type of the settings object to be created.
 
    .PARAMETER User
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER Token
        The access token to use to connect.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> New-MgaMailboxSettingObject -RestData $output -Type MailboxSettings
 
        Create a MSGraph.Exchange.MailboxSetting.MailboxSettings object from data in variable $output
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")]
    [CmdletBinding()]
    param (
        $RestData,

        [String]
        $Type,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [String]
        $FunctionName
    )
    Write-PSFMessage -Level Debug -Message "Create $($Type) mailbox Setting object" -Tag "CreateObject"

    if ($Type -notlike "TimeZoneSetting" -and $Type -notlike "ArchiveFolderSetting") {
        $name = [System.Web.HttpUtility]::UrlDecode(([uri]$RestData.'@odata.context').Fragment).TrimStart("#")
        $outputHash = [ordered]@{
            Name       = $name
            User       = $RestData.user
            BaseObject = $RestData
        }
    }

    switch ($Type) {
        {$_ -like 'AllSettings' -or $_ -like 'ArchiveFolderSetting'} {
            # create the full set of mailbox settings
            if ($RestData.archiveFolder) {
                try {
                    $archivFolder = Get-MgaMailFolder -Name $RestData.archiveFolder -User $User -Token $Token -ErrorAction Stop
                } catch {
                    Stop-PSFFunction -Message "Failed to get information about archiv folder on $($outputHash.Name)" -EnableException $true -Exception $_.Exception -Category ReadError -ErrorRecord $_ -Tag "QueryData" -FunctionName $FunctionName
                }

                if ($Type -like 'ArchiveFolderSetting') {
                    return $archivFolder
                } else {
                    $outputHash.Add("ArchiveFolder", $archivFolder)
                }
            } else {
                $archivFolder = ""
            }

            $timeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById($RestData.timeZone)
            $outputHash.Add("TimeZone", $timeZone)

            $autoReplySetting = [MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting]::new(
                [MSGraph.Exchange.MailboxSetting.AutomaticRepliesStatus]$RestData.automaticRepliesSetting.Status,
                [MSGraph.Exchange.MailboxSetting.ExternalAudienceScope]$RestData.automaticRepliesSetting.ExternalAudience,
                $RestData.automaticRepliesSetting.ExternalReplyMessage.Trim([char]65279),
                $RestData.automaticRepliesSetting.internalReplyMessage.Trim([char]65279),
                [MSGraph.Exchange.DateTimeTimeZone]$RestData.automaticRepliesSetting.ScheduledStartDateTime,
                [MSGraph.Exchange.DateTimeTimeZone]$RestData.automaticRepliesSetting.ScheduledEndDateTime,
                "$($name)/automaticRepliesSetting"
            )
            $outputHash.Add("AutomaticRepliesSetting", $autoReplySetting)

            $language = [MSGraph.Exchange.MailboxSetting.LocaleInfoSetting]::new(
                [cultureinfo]$RestData.language.locale,
                $RestData.language.displayName,
                "$($name)/language"
            )
            $outputHash.Add("Language", $language)

            $workingHours = [MSGraph.Exchange.MailboxSetting.WorkingHoursSetting]::new(
                $RestData.WorkingHours.daysOfWeek.ForEach( {[dayOfWeek]$_}),
                [datetime]$RestData.WorkingHours.startTime,
                [datetime]$RestData.WorkingHours.endTime,
                [MSGraph.Exchange.TimeZoneBase]::new($RestData.WorkingHours.timeZone.name),
                "$($name)/workingHours"
            )
            $outputHash.Add("WorkingHours", $workingHours)

            New-Object -TypeName MSGraph.Exchange.MailboxSetting.MailboxSettings -Property $outputHash
        }

        'AutomaticReplySetting' {
            # create auto reply settings object
            if ($RestData.automaticRepliesSetting) { $autoReplySetting = $RestData.automaticRepliesSetting } else { $autoReplySetting = $RestData }
            $outputHash.Add("Status", [MSGraph.Exchange.MailboxSetting.AutomaticRepliesStatus]$autoReplySetting.Status)
            $outputHash.Add("ExternalAudience", [MSGraph.Exchange.MailboxSetting.ExternalAudienceScope]$autoReplySetting.ExternalAudience)
            $outputHash.Add("ExternalReplyMessage", $autoReplySetting.ExternalReplyMessage.Trim([char]65279))
            $outputHash.Add("InternalReplyMessage", $autoReplySetting.internalReplyMessage.Trim([char]65279))
            $outputHash.Add("ScheduledStartDateTimeUTC", [MSGraph.Exchange.DateTimeTimeZone]$autoReplySetting.ScheduledStartDateTime)
            $outputHash.Add("ScheduledEndDateTimeUTC", [MSGraph.Exchange.DateTimeTimeZone]$autoReplySetting.ScheduledEndDateTime)

            $output = New-Object -TypeName MSGraph.Exchange.MailboxSetting.AutomaticRepliesSetting -Property $outputHash
            $output
            Remove-Variable -Name autoReplySetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore
        }

        'LanguageSetting' {
            # create language setting object
            if($RestData.language) { $languageSetting = $RestData.language } else { $languageSetting = $RestData }
            $outputHash.Add("Locale", [cultureinfo]$languageSetting.locale)
            $outputHash.Add("DisplayName", $languageSetting.displayName)

            $output = New-Object -TypeName MSGraph.Exchange.MailboxSetting.LocaleInfoSetting -Property $outputHash
            $output
            Remove-Variable -Name languageSetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore
        }

        'TimeZoneSetting' {
            # create timeZone object
            if($RestData.timeZone) { $timeZoneSetting = $RestData.timeZone } else { $timeZoneSetting = $RestData }
            $output = [System.TimeZoneInfo]::FindSystemTimeZoneById($timeZoneSetting)
            $output
            Remove-Variable -Name timeZoneSetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore
        }

        'WorkingHoursSetting' {
            # create workingHours object
            if($RestData.workingHours) { $workingHourSetting = $RestData.workingHours } else { $workingHourSetting = $RestData }
            $outputHash.Add("DaysOfWeek", $workingHourSetting.daysOfWeek.ForEach( {[dayOfWeek]$_}))
            $outputHash.Add("StartTime", [datetime]$workingHourSetting.startTime)
            $outputHash.Add("EndTime", [datetime]$workingHourSetting.endTime)
            $outputHash.Add("TimeZone", [MSGraph.Exchange.TimeZoneBase]::new($workingHourSetting.timeZone.name))

            $output = New-Object -TypeName MSGraph.Exchange.MailboxSetting.WorkingHoursSetting -Property $outputHash
            $output
            Remove-Variable -Name workingHourSetting -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -WarningAction Ignore -ErrorAction Ignore
        }

        Default {
            Stop-PSFFunction -Message "Unable to output a valid MailboxSetting object, because of unhandled type '$($Type)'. Developer mistake." -EnableException $true -Category InvalidData -FunctionName $MyInvocation.MyCommand
        }
    }
}

function Get-MgaAccessTokenRegistered {
    <#
    .SYNOPSIS
        Output the registered access token
 
    .DESCRIPTION
        Output the registered access token
 
    .EXAMPLE
        PS C:\> Get-MgaRegisteredAccessToken
 
        Output the registered access token
    #>

    [CmdletBinding()]
    [Alias('Get-MgaRegisteredAccessToken')]
    param ()

    if ($script:msgraph_Token) {
        $script:msgraph_Token
    } else {
        Write-PSFMessage -Level Host -Message "No access token registered."
    }
}

function Invoke-MgaRestMethodDelete {
    <#
    .SYNOPSIS
        Performs a REST DELETE against the graph API
 
    .DESCRIPTION
        Performs a REST DELETE against the graph API.
        Primarily used for internal commands.
 
    .PARAMETER Field
        The api child item under the username in the url of the api call.
        If this didn't make sense to you, you probably shouldn't be using this command ;)
 
    .PARAMETER User
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER Body
        JSON date as string to send as body on the REST call
 
    .PARAMETER ContentType
        Nature of the data in the body of an entity. Required.
 
    .PARAMETER ApiConnection
        The URI for the Microsoft Graph connection
 
    .PARAMETER ApiVersion
        The version used for queries in Microsoft Graph connection
 
    .PARAMETER Token
        The access token to use to connect.
 
    .PARAMETER Force
        If specified the user will not prompted on confirmation.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
 
    .EXAMPLE
        PS C:\> Invoke-MgaRestMethodDelete -Field "mailFolders/$($id)"
 
        Delete a mailfolder with the id stored in variable $id.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [Alias('Invoke-MgaDeleteMethod')]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Field,

        [string]
        $User,

        [String]
        $Body,

        [ValidateSet("application/json")]
        [String]
        $ContentType = "application/json",

        [String]
        $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'),

        [string]
        $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'),

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $Force,

        [string]
        $FunctionName = $MyInvocation.MyCommand
    )

    # tokek check
    $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName

    if (-not $User) { $User = $Token.UserprincipalName }
    $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)"

    Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Invoking REST DELETE to uri: $($restUri)"
    Write-PSFMessage -Tag "RestData" -Level Debug -Message "REST body data: $($Body)"

    Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore
    $invokeParam = @{
        Method  = "DELETE"
        Uri     = $restUri
        Body    = $Body
        Headers = @{
            "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )"
            "Content-Type"  = $ContentType
        }
    }

    if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess($restUri, "Invoke DELETE") }
    if ($doAction) {
        try {
            $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing
        } catch {
            Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue
            Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName
        }
        Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue
    }

    if ($data) {
        $data | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force
        $data
    }
}

function Invoke-MgaRestMethodGet {
    <#
    .SYNOPSIS
        Performs a rest GET against the graph API
 
    .DESCRIPTION
        Performs a rest GET against the graph API.
        Primarily used for internal commands.
 
    .PARAMETER Field
        The api child item under the username in the url of the api call.
        If this didn't make sense to you, you probably shouldn't be using this command ;)
 
    .PARAMETER User
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER Delta
        Indicates that the query is intend to be a delta query, so a delta-link property is added to the output-object ('@odata.deltaLink').
 
    .PARAMETER DeltaLink
        Specifies the uri to query for delta objects on a query.
 
    .PARAMETER UserUnspecific
        Specfies that no user name or "me" should be added in uri for api call.
        This is used for calling "all company data" like "available teams" or such things.
 
    .PARAMETER ResultSize
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER ApiConnection
        The URI for the Microsoft Graph connection
 
    .PARAMETER ApiVersion
        The version used for queries in Microsoft Graph connection
 
    .PARAMETER Token
        The access token to use to connect.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> Invoke-MgaRestMethodGet -Field 'mailFolders' -Token $Token -User $User
 
        Retrieves a list of email folders for the user $User, using the token stored in $Token
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [Alias('Invoke-MgaGetMethod')]
    param (
        [ValidateNotNullOrEmpty()]
        [string]
        $Field,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'DeltaLink')]
        [string]
        $User,

        [Parameter(ParameterSetName = 'Default')]
        [switch]
        $Delta,

        [Parameter(ParameterSetName = 'DeltaLink')]
        [string]
        $DeltaLink,

        [Parameter(ParameterSetName = 'UserUnspecific')]
        [Alias('NoUserName', "NoUser", "NoUserSpecific")]
        [switch]
        $UserUnspecific,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [String]
        $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'),

        [string]
        $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'),

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [string]
        $FunctionName = $MyInvocation.MyCommand
    )

    # tokek check
    $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName

    #region variable definition
    if ($PSCmdlet.ParameterSetName -like "DeltaLink") {
        Write-PSFMessage -Level VeryVerbose -Message "ParameterSet $($PSCmdlet.ParameterSetName) - constructing delta query" -Tag "ParameterSetHandling"
        $restUri = $DeltaLink
        $Delta = $true
        $User = ([uri]$restUri).AbsolutePath.split('/')[2]
    } elseif ($PSCmdlet.ParameterSetName -like "UserUnspecific") {
        $restUri = "$($ApiConnection)/$($ApiVersion)/$($Field)"
    } else {
        if (-not $User) { $User = $Token.UserprincipalName }
        $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)"
        if ($Delta) { $restUri = $restUri + "/delta" }
    }
    if ($ResultSize -eq 0) { $ResultSize = [Int64]::MaxValue }
    [Int64]$i = 0
    [Int64]$overResult = 0
    $tooManyItems = $false
    $output = @()
    #endregion variable definition

    #region query data
    do {
        Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Get REST data: $($restUri)"

        Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore
        $invokeParam = @{
            Method  = "Get"
            Uri     = $restUri
            Headers = @{
                "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )"
                "Content-Type"  = "application/json"
            }
        }

        try {
            $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing
        } catch {
            Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue
            Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName
        }
        Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue

        if ("Value" -in $data.psobject.Properties.Name) {
            # Multi object with value property returned by api call
            [array]$value = $data.Value
            Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Retrieving $($value.Count) records from query"
            $i = $i + $value.Count
            if ($i -lt $ResultSize) {
                $restUri = $data.'@odata.nextLink'
            } else {
                $restUri = ""
                $tooManyItems = $true
                $overResult = $ResultSize - ($i - $value.Count)
                Write-PSFMessage -Tag "ResultSize" -Level Verbose -Message "Resultsize ($ResultSize) exeeded. Output $($overResult) object(s) in record set."
            }
        } else {
            # Single object without value property returned by api call
            Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Single item retrived. Outputting data."
            [array]$value = $data
            $restUri = ""
        }

        if ((-not $tooManyItems) -or ($overResult -gt 0)) {
            # check if resultsize is reached
            if ($overResult -gt 0) {
                $output = $output + $Value[0..($overResult - 1)]
            } else {
                $output = $output + $Value
            }
        }
    } while ($restUri)
    #endregion query data

    #region output data
    $output | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force
    if ($Delta) {
        if ('@odata.deltaLink' -in $data.psobject.Properties.Name) {
            $output | Add-Member -MemberType NoteProperty -Name '@odata.deltaLink' -Value $data.'@odata.deltaLink' -PassThru
        } else {
            $output | Add-Member -MemberType NoteProperty -Name '@odata.deltaLink' -Value $data.'@odata.nextLink' -PassThru
        }
    } else {
        $output
    }

    if ($tooManyItems) {
        # write information to console if resultsize exceeds
        if ($Delta) {
            Write-PSFMessage -Tag "GetData" -Level Host -Message "Reaching maximum ResultSize before finishing delta query. Next delta query will continue on pending objects. Current ResultSize: $($ResultSize)" -FunctionName $FunctionName
        } else {
            Write-PSFMessage -Tag "GetData" -Level Warning -Message "Too many items. Reaching maximum ResultSize before finishing query. You may want to increase the ResultSize. Current ResultSize: $($ResultSize)" -FunctionName $FunctionName
        }
    }
    #endregion output data
}

function Invoke-MgaRestMethodPatch {
    <#
    .SYNOPSIS
        Performs a REST PATCH against the graph API
 
    .DESCRIPTION
        Performs a REST PATCH against the graph API.
        Primarily used for internal commands.
 
    .PARAMETER Field
        The api child item under the username in the url of the api call.
        If this didn't make sense to you, you probably shouldn't be using this command ;)
 
    .PARAMETER User
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER Body
        JSON date as string to send as body on the REST call
 
    .PARAMETER ContentType
        Nature of the data in the body of an entity. Required.
 
    .PARAMETER ApiConnection
        The URI for the Microsoft Graph connection
 
    .PARAMETER ApiVersion
        The version used for queries in Microsoft Graph connection
 
    .PARAMETER Token
        The access token to use to connect.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> Invoke-MgaRestMethodPatch -Field "messages/$($id)" -Body '{ "isRead": true }' -Token $Token
 
        Set a message as readed.
        The token stored in $Token is used for the api call.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [Alias('Invoke-MgaPatchMethod')]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Field,

        [string]
        $User,

        [String]
        $Body,

        [ValidateSet("application/json")]
        [String]
        $ContentType = "application/json",

        [String]
        $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'),

        [string]
        $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'),

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [string]
        $FunctionName = $MyInvocation.MyCommand
    )

    # tokek check
    $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName

    if (-not $User) { $User = $Token.UserprincipalName }
    $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)"

    Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Invoking REST PATCH to uri: $($restUri)"
    Write-PSFMessage -Tag "RestData" -Level Debug -Message "REST body data: $($Body)"

    Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore
    $invokeParam = @{
        Method  = "Patch"
        Uri     = $restUri
        Body    = $Body
        Headers = @{
            "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )"
            "Content-Type"  = $ContentType
        }
    }
    try {
        $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing
    } catch {
        Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue
        Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName
    }
    Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue

    if ($data) {
        $data | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force
        $data
    }
}

function Invoke-MgaRestMethodPost {
    <#
    .SYNOPSIS
        Performs a REST POST against the graph API
 
    .DESCRIPTION
        Performs a REST POST against the graph API.
        Primarily used for internal commands.
 
    .PARAMETER Field
        The api child item under the username in the url of the api call.
        If this didn't make sense to you, you probably shouldn't be using this command ;)
 
    .PARAMETER User
        The user to execute this under. Defaults to the user the token belongs to.
 
    .PARAMETER Body
        JSON date as string to send as body on the REST call
 
    .PARAMETER ContentType
        Nature of the data in the body of an entity. Required.
 
    .PARAMETER ApiConnection
        The URI for the Microsoft Graph connection
 
    .PARAMETER ApiVersion
        The version used for queries in Microsoft Graph connection
 
    .PARAMETER Token
        The access token to use to connect.
 
    .PARAMETER FunctionName
        Name of the higher function which is calling this function.
 
    .EXAMPLE
        PS C:\> Invoke-MgaRestMethodPost -Field "messages/$($id)/reply" -Body '{"comment": "comment-value"}' -Token $Token
 
        Reply to the sender of a message with the id, stored in variable $id. The message is then saved in the Sent Items folder.
        The token stored in $Token is used for the api call.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [Alias('Invoke-MgaPostMethod')]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Field,

        [string]
        $User,

        [String]
        $Body,

        [ValidateSet("application/json")]
        [String]
        $ContentType = "application/json",

        [String]
        $ApiConnection = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiConnection' -Fallback 'https://graph.microsoft.com'),

        [string]
        $ApiVersion = (Get-PSFConfigValue -FullName 'MSGraph.Tenant.ApiVersion' -Fallback 'v1.0'),

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [string]
        $FunctionName = $MyInvocation.MyCommand
    )

    # tokek check
    $Token = Invoke-TokenLifetimeValidation -Token $Token -FunctionName $FunctionName

    if (-not $User) { $User = $Token.UserprincipalName }
    $restUri = "$($ApiConnection)/$($ApiVersion)/$(Resolve-UserString -User $User)/$($Field)"

    Write-PSFMessage -Tag "RestData" -Level VeryVerbose -Message "Invoking REST POST to uri: $($restUri)"
    Write-PSFMessage -Tag "RestData" -Level Debug -Message "REST body data: $($Body)"

    Clear-Variable -Name data -Force -WhatIf:$false -Confirm:$false -Verbose:$false -ErrorAction Ignore
    $invokeParam = @{
        Method  = "Post"
        Uri     = $restUri
        Body    = $Body
        Headers = @{
            "Authorization" = "Bearer $( [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token.AccessToken)) )"
            "Content-Type"  = $ContentType
        }
    }

    try {
        $data = Invoke-RestMethod @invokeParam -ErrorVariable "restError" -ErrorAction Stop -Verbose:$false -UseBasicParsing
    } catch {
        Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue
        Stop-PSFFunction -Tag "RestDataError" -Message $_.Exception.Message -Exception $_.Exception -ErrorRecord $_ -EnableException $true -Category ConnectionError -FunctionName $FunctionName
    }
    Remove-Variable -Name invokeParam -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false -ErrorAction:SilentlyContinue

    if ($restError) {
        Stop-PSFFunction -Tag "RestData" -Message $parseError[0].Exception -Exception $parseError[0].Exception -EnableException $false -Category ConnectionError -FunctionName $FunctionName
        return
    }

    if ($data) {
        $data | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -Force
        $data
    }
}

function New-MgaAccessToken {
    <#
    .SYNOPSIS
        Creates an access token for contacting the specified application endpoint
 
    .DESCRIPTION
        Creates an access token for contacting the specified application endpoint
 
    .PARAMETER MailboxName
        The email address of the mailbox to access
 
    .PARAMETER Credential
        The credentials to use to authenticate the request.
        Using this avoids the need to visually interact with the logon screen.
        Only works for accounts that have once logged in visually, but can be used from any machine.
 
    .PARAMETER ClientId
        The ID of the client to connect with.
        This is the ID of the registered application.
 
    .PARAMETER RedirectUrl
        Some weird vodoo. Leave it as it is, unless you know better
 
    .PARAMETER ShowLoginWindow
        Force to show login window with account selection again.
 
    .PARAMETER Register
        Registers the token, so all subsequent calls to Exchange Online reuse it by default.
 
    .PARAMETER PassThru
        Outputs the token to the console, even when the register switch is set
 
    .PARAMETER IdentityPlatformVersion
        Specifies the endpoint version of the logon platform (Microsoft identity platform) where to connect for logon.
        Use 2.0 if you want to login with a Microsoft Account.
 
        For more information goto https://docs.microsoft.com/en-us/azure/active-directory/develop/about-microsoft-identity-platform
 
    .PARAMETER Tenant
        The entry point to sign into.
        The allowed values are common, organizations, consumers.
 
    .PARAMETER Permission
        Only applies if IdentityPlatformVersion version 2.0 is used.
        Specify the requested permission in the token.
 
    .PARAMETER ResourceUri
        The App ID URI of the target web API (secured resource).
        It may be https://graph.microsoft.com
 
    .EXAMPLE
        PS C:\> New-MgaAccessToken -Register
 
        For best usage and convinience, mostly, this is what you want to use.
 
        Requires an interactive session with a user handling the web UI.
        For addition the aquired token will be registered in the module as default value to use with all the commands.
 
    .EXAMPLE
        PS C:\> $token = New-MgaAccessToken
 
        Requires an interactive session with a user handling the web UI.
 
    .EXAMPLE
        PS C:\> $token = New-MgaAccessToken -Credential $cred
 
        Generates a token with the credentials specified in $cred.
        This is not supported for personal accounts (Micrsoft Accounts).
 
    .EXAMPLE
        PS C:\> New-MgaAccessToken -Register -ShowLoginWindow -ClientId '4a6acbac-d325-47a3-b59b-d2e9e05a37c1' -RedirectUrl 'urn:ietf:wg:oauth:2.0:oob' -IdentityPlatformVersion '2.0'
 
        Requires an interactive session with a user handling the web UI.
        Always prompt for account selection windows.
        Connecting against Azure Application with ID '4a6acbac-d325-47a3-b59b-d2e9e05a37c1'.
        Specifies RedirectUrl 'urn:ietf:wg:oauth:2.0:oob' (default value for interactive apps).
        Use Authentication Plattform 1.0, which only allows AzureAD business accounts to logon.
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(DefaultParameterSetName = "LoginWithWebForm")]
    [Alias('Connect-MgaGraph')]
    param (
        [Parameter(ParameterSetName = 'LoginWithCredentialObject')]
        [PSCredential]
        $Credential,

        [System.Guid]
        $ClientId = (Get-PSFConfigValue -FullName MSGraph.Tenant.Application.ClientID -NotNull),

        [string]
        $RedirectUrl = (Get-PSFConfigValue -FullName MSGraph.Tenant.Application.RedirectUrl -Fallback "urn:ietf:wg:oauth:2.0:oob"),

        [Parameter(ParameterSetName = 'LoginWithWebForm')]
        [Alias('Force')]
        [switch]
        $ShowLoginWindow,

        [ValidateSet('1.0', '2.0')]
        [string]
        $IdentityPlatformVersion = (Get-PSFConfigValue -FullName MSGraph.Tenant.Authentiation.IdentityPlatformVersion -Fallback '2.0'),

        [String[]]
        $Permission,

        [String]
        $ResourceUri = (Get-PSFConfigValue -FullName MSGraph.Tenant.ApiConnection -Fallback 'https://graph.microsoft.com'),

        [ValidateSet('common', 'organizations', 'consumers')]
        [String]
        $Tenant = 'common',

        [switch]
        $Register,

        [switch]
        $PassThru
    )

    begin {
        $baselineTimestamp = [datetime]"1970-01-01Z00:00:00"
        $endpointBaseUri = (Get-PSFConfigValue -FullName MSGraph.Tenant.Authentiation.Endpoint -Fallback 'https://login.microsoftonline.com')

        if ($IdentityPlatformVersion -like '1.0' -and $Permission) {
            Write-PSFMessage -Level Warning -Message "Individual pemissions are not supported in combination with IdentityPlatformVersion 1.0. Specified Permission ($([String]::Join(", ", $Permission))) in parameter will be ignored" -Tag "ParameterSetHandling"
            $Permission = ""
        }
    }

    process {
        #region variable definitions
        switch ($IdentityPlatformVersion) {
            '1.0' { $endpointUri = "$($endpointBaseUri)/$($Tenant)/oauth2" }
            '2.0' {
                if ($Credential -and ($Tenant -notlike "organizations")) {
                    $endpointUri = "$($endpointBaseUri)/organizations/oauth2/V2.0"
                } else {
                    $endpointUri = "$($endpointBaseUri)/$($Tenant)/oauth2/V2.0"
                }
            }
        }

        $endpointUriAuthorize = "$($endpointUri)/authorize"
        $endpointUriToken = "$($endpointUri)/token"
        Write-PSFMessage -Level Verbose -Message "Start authentication against endpoint $($endpointUri). (Identity platform version $($IdentityPlatformVersion))" -Tag "Authorization"
        Write-PSFMessage -Level VeryVerbose -Message "Try to get token for usage of application ClientID: $($ClientId) to interact with ResourceAPI: $($ResourceUri)" -Tag "Authorization"

        if ($IdentityPlatformVersion -like '2.0') {
            [array]$scopes = "offline_access", "openid" # offline_access to get refreshtoken
            foreach ($permissionItem in $Permission) {
                $scopes = $scopes + "$($resourceUri)/$($permissionItem)"
            }
            $scope = [string]::Join(" ", $scopes)
            Remove-Variable -Name scopes -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false
            Write-PSFMessage -Level VeryVerbose -Message "Using scope: $($scope)" -Tag "Authorization"
        }
        #endregion variable definitions


        #region Request an authorization code (login procedure)
        if (-not $Credential) {
            # build authorization string with web form
            # Info https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#request-an-authorization-code
            Write-PSFMessage -Level Verbose -Message "Authentication is done by code. Query authentication from login form." -Tag "Authorization"

            $queryHash = [ordered]@{
                client_id     = "$($ClientId)"
                response_type = "code"
                redirect_uri  = [System.Web.HttpUtility]::UrlEncode($redirectUrl)
            }
            switch ($IdentityPlatformVersion) {
                '1.0' {
                    $queryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) # optional, but recommended
                    if ($ShowLoginWindow) { $queryHash.Add("prompt", "select_account") }
                }

                '2.0' {
                    $queryHash.Add("scope", [uri]::EscapeDataString($scope))
                    if ($ShowLoginWindow) { $queryHash.Add("prompt", "login") }
                }
            }

            # Show login windows (web form)
            [string]$url = $endpointUriAuthorize + (Convert-UriQueryFromHash $queryHash)
            $phase1auth = Show-OAuthWindow -Url $url
            if (-not $phase1auth.code) {
                $msg = "Authentication failed. Unable to obtain AccessToken.`n$($phase1auth.error_description)"
                if ($phase1auth.error) { $msg = $phase1auth.error.ToUpperInvariant() + " - " + $msg }
                Stop-PSFFunction -Message $msg -Tag "Authorization" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
            }
            Remove-Variable -Name url -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false

            # build authorization string with authentication code from web form auth
            $tokenQueryHash = [ordered]@{
                client_id    = "$($ClientId)"
                grant_type   = "authorization_code"
                code         = "$($phase1auth.code)"
                redirect_uri = "$($redirectUrl)"
            }
            switch ($IdentityPlatformVersion) {
                '1.0' { $tokenQueryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) }
                '2.0' { $tokenQueryHash.Add("scope", [uri]::EscapeDataString($scope)) }
            }
            $authorizationPostRequest = Convert-UriQueryFromHash $tokenQueryHash -NoQuestionmark
        } else {
            # build authorization string with plain text credentials
            # Info https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow#request-an-access-token
            Write-PSFMessage -Level Verbose -Message "Authentication is done by specified credentials. (No TwoFactor-Authentication supported!)" -Tag "Authorization"

            $tokenQueryHash = [ordered]@{
                grant_type = "password"
                username   = $Credential.UserName
                password   = $Credential.GetNetworkCredential().password
                client_id  = $ClientId
            }
            switch ($IdentityPlatformVersion) {
                '1.0' { $tokenQueryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) }
                '2.0' { $tokenQueryHash.Add("scope", [uri]::EscapeDataString($scope)) }
            }

            $authorizationPostRequest = Convert-UriQueryFromHash $tokenQueryHash -NoQuestionmark
        }
        #endregion Request an authorization code (login procedure)


        #region Request an access token
        $content = New-Object -TypeName "System.Net.Http.StringContent" -ArgumentList ($authorizationPostRequest, [System.Text.Encoding]::UTF8, "application/x-www-form-urlencoded")
        $httpClient = New-HttpClient
        $clientResult = $httpClient.PostAsync([Uri]($endpointUriToken), $content)
        if ($clientResult.Result.StatusCode -eq [System.Net.HttpStatusCode]"OK") {
            Write-PSFMessage -Level Verbose -Message "AccessToken granted. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase)" -Tag "Authorization"
        } else {
            $httpClient.CancelPendingRequests()
            $msg = "Request for AccessToken failed. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase) `n$($jsonResponse.error_description)"
            Stop-PSFFunction -Message $msg -Tag "Authorization" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
        }
        #endregion Request an access token


        #region Build output object
        $jsonResponse = ConvertFrom-Json -InputObject $clientResult.Result.Content.ReadAsStringAsync().Result -ErrorAction Ignore

        $resultObject = New-Object -TypeName MSGraph.Core.AzureAccessToken -Property @{
            IdentityPlatformVersion = $IdentityPlatformVersion
            TokenType               = $jsonResponse.token_type
            AccessToken             = $null
            RefreshToken            = $null
            IDToken                 = $null
            Credential              = $Credential
            ClientId                = $ClientId
            Resource                = $resourceUri
            AppRedirectUrl          = $RedirectUrl
        }

        switch ($IdentityPlatformVersion) {
            '1.0' {
                $resultObject.Scope = $jsonResponse.scope -split " "
                $resultObject.ValidUntilUtc = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToUniversalTime()
                $resultObject.ValidFromUtc = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToUniversalTime()
                $resultObject.ValidUntil = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().IsDaylightSavingTime() )
                $resultObject.ValidFrom = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().IsDaylightSavingTime() )
            }
            '2.0' {
                $resultObject.Scope = $jsonResponse.scope.Replace("$ResourceUri/", '') -split " "
                $resultObject.ValidUntilUtc = (Get-Date).AddSeconds($jsonResponse.expires_in).ToUniversalTime()
                $resultObject.ValidFromUtc = (Get-Date).ToUniversalTime()
                $resultObject.ValidUntil = (Get-Date).AddSeconds($jsonResponse.expires_in).ToLocalTime()
                $resultObject.ValidFrom = (Get-Date).ToLocalTime()
            }
        }

        # Insert token data into output object. done as secure string to prevent text output of tokens
        if ($jsonResponse.psobject.Properties.name -contains "refresh_token") {
            $resultObject.RefreshToken = ($jsonResponse.refresh_token | ConvertTo-SecureString -AsPlainText -Force)
        }
        if ($jsonResponse.psobject.Properties.name -contains "id_token") {
            $resultObject.IDToken = ($jsonResponse.id_token | ConvertTo-SecureString -AsPlainText -Force)
            $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.id_token
        }
        if ($jsonResponse.psobject.Properties.name -contains "access_token") {
            $resultObject.AccessToken = ($jsonResponse.access_token | ConvertTo-SecureString -AsPlainText -Force)
            if ($jsonResponse.access_token.Contains(".") -and $jsonResponse.access_token.StartsWith("eyJ")) {
                $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.access_token
            }
        }

        # Getting validity period out of AccessToken information
        if ($resultObject.AccessTokenInfo -and
            ($resultObject.AccessTokenInfo.TenantID.ToString() -notlike "9188040d-6c67-4c5b-b112-36a304b66dad")
        ) {
            $resultObject.ValidUntilUtc = $resultObject.AccessTokenInfo.ExpirationTime.ToUniversalTime()
            $resultObject.ValidFromUtc = $resultObject.AccessTokenInfo.NotBefore.ToUniversalTime()
            $resultObject.ValidUntil = $resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().IsDaylightSavingTime() )
            $resultObject.ValidFrom = $resultObject.AccessTokenInfo.NotBefore.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.NotBefore.ToLocalTime().IsDaylightSavingTime() )
        }
        #endregion Build output object


        #region Output the object
        # Checking if token is valid
        # ToDo implement "validating token information" -> https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens
        if ($resultObject.IsValid) {
            if ($Register) {
                $script:msgraph_Token = $resultObject
                if ($PassThru) { $resultObject }
            } else {
                $resultObject
            }
        } else {
            Stop-PSFFunction -Message "Token failure. Acquired token is not valid" -EnableException $true -Tag "Authorization"
        }
        #endregion Output the object
    }

    end {}
}


function Register-MgaAccessToken {
    <#
    .SYNOPSIS
        Registers an access token
 
    .DESCRIPTION
        Registers an access token, so all subsequent calls to Exchange Online reuse it by default.
 
    .PARAMETER Token
        The Token to register as default token for subsequent calls.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .EXAMPLE
        PS C:\> Get-MgaRegisteredAccessToken
 
        Output the registered access token
    #>

    [CmdletBinding (SupportsShouldProcess = $false, ConfirmImpact = 'Medium')]
    [OutputType([MSGraph.Core.AzureAccessToken])]
    param (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)]
        [ValidateNotNullOrEmpty()]
        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru

    )

    begin {}

    process{
        $script:msgraph_Token = $Token
        if ($PassThru) {
            $script:msgraph_Token
        }
    }

    end {}
}

function Update-MgaAccessToken {
    <#
    .SYNOPSIS
        Updates an existing access token
 
    .DESCRIPTION
        Updates an existing access token for contacting the specified application endpoint as long
        as the token is still valid. Otherwise, a new access is called through New-MgaAccessToken.
 
    .PARAMETER Token
        The token object to renew.
 
    .PARAMETER Register
        Registers the renewed token, so all subsequent calls to Exchange Online reuse it by default.
 
    .PARAMETER PassThru
        Outputs the token to the console, even when the register switch is set
 
    .EXAMPLE
        PS C:\> Update-MgaAccessToken -Register
 
        Updates the default (registered) Accesstoken and register it again as the default token
 
    .EXAMPLE
        PS C:\> $token = Update-MgaAccessToken -Token $token
 
        Updates the AccessToken in $token and output a new AccessToken object.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(DefaultParameterSetName = "Default")]
    param (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [MSGraph.Core.AzureAccessToken]
        $Token,

        [Parameter(ParameterSetName = 'Register')]
        [switch]
        $Register,

        [Parameter(ParameterSetName = 'Register')]
        [switch]
        $PassThru
    )

    begin {
        $endpointBaseUri = (Get-PSFConfigValue -FullName MSGraph.Tenant.Authentiation.Endpoint -Fallback 'https://login.microsoftonline.com')
        $baselineTimestamp = [datetime]"1970-01-01Z00:00:00"
    }

    process {
        $Token = Resolve-Token -Token $Token -FunctionName $MyInvocation.MyCommand

        $Credential = $Token.Credential
        $ClientId = $Token.ClientId
        $RedirectUrl = $Token.AppRedirectUrl.ToString()
        $ResourceUri = $Token.Resource.ToString().TrimEnd('/')
        $Permission = ($Token.Scope | Where-Object { $_ -notin "offline_access", "openid", "profile", "email" })
        $IdentityPlatformVersion = $Token.IdentityPlatformVersion

        if (-not $Token.IsValid) {
            Write-PSFMessage -Level Warning -Message "Token lifetime already expired and can't be newed. New authentication is required. Calling New-MgaAccessToken..." -Tag "Authorization"

            $paramsNewToken = @{
                PassThru                = $True
                ClientId                = $ClientId
                RedirectUrl             = $RedirectUrl
                ResourceUri             = $ResourceUri
                Permission              = $Permission
                IdentityPlatformVersion = $IdentityPlatformVersion
            }
            if ($Credential) { $paramsNewToken.Add("Credential", $Credential ) }
            if ($Register -or ($script:msgraph_Token.AccessTokenInfo.Payload -eq $Token.AccessTokenInfo.Payload) ) { $paramsNewToken.Add("Register", $true) }
            if (Test-PSFParameterBinding -ParameterName Verbose) { $paramsNewToken.Add("Verbose", $true) }

            $resultObject = New-MgaAccessToken @paramsNewToken
            if ($PassThru) { return $resultObject } else { return }
        }

        Write-PSFMessage -Level Verbose -Message "Start token refresh for application $( if($Token.AppName){$Token.AppName}else{$ClientId} ). (Identity platform version $($IdentityPlatformVersion))" -Tag "Authorization"

        switch ($IdentityPlatformVersion) {
            '1.0' { $endpointUriToken = "$($endpointBaseUri)/common/oauth2/token" }
            '2.0' {
                if ($token.Credential) {
                    $endpointUriToken = "$($endpointBaseUri)/organizations/oauth2/V2.0/token"
                } else {
                    $endpointUriToken = "$($endpointBaseUri)/common/oauth2/V2.0/token"
                }

                [array]$scopes = "offline_access", "openid"
                foreach ($permissionItem in $Permission) {
                    $scopes = $scopes + "$($resourceUri)/$($permissionItem)"
                }
                $scope = [string]::Join(" ", $scopes)
                Remove-Variable -Name scopes -Force -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false
                Write-PSFMessage -Level VeryVerbose -Message "Using scope: $($scope)" -Tag "Authorization"
            }
        }

        $queryHash = [ordered]@{
            grant_type    = "refresh_token"
            client_id     = $ClientId
            refresh_token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Token.RefreshToken))
        }
        switch ($IdentityPlatformVersion) {
            '1.0' { $queryHash.Add("resource", [System.Web.HttpUtility]::UrlEncode($resourceUri)) }
            '2.0' {
                $queryHash.Add("scope", [uri]::EscapeDataString($scope))
                $queryHash.Add("redirect_uri", [System.Web.HttpUtility]::UrlEncode($redirectUrl))
            }
        }
        $authorizationPostRequest = Convert-UriQueryFromHash $queryHash -NoQuestionmark

        $content = New-Object System.Net.Http.StringContent($authorizationPostRequest, [System.Text.Encoding]::UTF8, "application/x-www-form-urlencoded")
        $httpClient = New-HttpClient
        $clientResult = $httpClient.PostAsync([Uri]$endpointUriToken, $content)
        $jsonResponse = ConvertFrom-Json -InputObject $clientResult.Result.Content.ReadAsStringAsync().Result -ErrorAction Ignore
        if ($clientResult.Result.StatusCode -eq [System.Net.HttpStatusCode]"OK") {
            Write-PSFMessage -Level Verbose -Message "AccessToken renewal successful. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase)" -Tag "Authorization"
        } else {
            $httpClient.CancelPendingRequests()
            $msg = "Request for AccessToken failed. $($clientResult.Result.StatusCode.value__) ($($clientResult.Result.StatusCode)) $($clientResult.Result.ReasonPhrase) `n$($jsonResponse.error_description)"
            Stop-PSFFunction -Message $msg -Tag "Authorization" -EnableException $true -Exception ([System.Management.Automation.RuntimeException]::new($msg))
        }

        # Build output object
        $resultObject = New-Object -TypeName MSGraph.Core.AzureAccessToken -Property @{
            IdentityPlatformVersion = $IdentityPlatformVersion
            TokenType               = $jsonResponse.token_type
            AccessToken             = $null
            RefreshToken            = $null
            IDToken                 = $null
            Credential              = $Credential
            ClientId                = $ClientId
            Resource                = $resourceUri
            AppRedirectUrl          = $RedirectUrl
        }
        switch ($IdentityPlatformVersion) {
            '1.0' {
                $resultObject.Scope = $jsonResponse.scope -split " "
                $resultObject.ValidUntilUtc = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToUniversalTime()
                $resultObject.ValidFromUtc = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToUniversalTime()
                $resultObject.ValidUntil = $baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.expires_on).ToLocalTime().IsDaylightSavingTime() )
                $resultObject.ValidFrom = $baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().AddHours( [int]$baselineTimestamp.AddSeconds($jsonResponse.not_before).ToLocalTime().IsDaylightSavingTime() )
            }
            '2.0' {
                $resultObject.Scope = $jsonResponse.scope.Replace("$ResourceUri/", '') -split " "
                $resultObject.ValidUntilUtc = (Get-Date).AddSeconds($jsonResponse.expires_in).ToUniversalTime()
                $resultObject.ValidFromUtc = (Get-Date).ToUniversalTime()
                $resultObject.ValidUntil = (Get-Date).AddSeconds($jsonResponse.expires_in).ToLocalTime()
                $resultObject.ValidFrom = (Get-Date).ToLocalTime()
            }
        }

        # Insert token data into output object. done as secure string to prevent text output of tokens
        if ($jsonResponse.psobject.Properties.name -contains "refresh_token") { $resultObject.RefreshToken = ($jsonResponse.refresh_token | ConvertTo-SecureString -AsPlainText -Force) }
        if ($jsonResponse.psobject.Properties.name -contains "id_token") {
            $resultObject.IDToken = ($jsonResponse.id_token | ConvertTo-SecureString -AsPlainText -Force)
            $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.id_token
        }
        if ($jsonResponse.psobject.Properties.name -contains "access_token") {
            $resultObject.AccessToken = ($jsonResponse.access_token | ConvertTo-SecureString -AsPlainText -Force)
            if ($jsonResponse.access_token.Contains(".") -and $jsonResponse.access_token.StartsWith("eyJ")) {
                $resultObject.AccessTokenInfo = ConvertFrom-JWTtoken -Token $jsonResponse.access_token
            }
        }

        # Getting validity period out of AccessToken information
        if ($resultObject.AccessTokenInfo -and $resultObject.AccessTokenInfo.TenantID.ToString() -notlike "9188040d-6c67-4c5b-b112-36a304b66dad") {
            $resultObject.ValidUntilUtc = $resultObject.AccessTokenInfo.ExpirationTime.ToUniversalTime()
            $resultObject.ValidFromUtc = $resultObject.AccessTokenInfo.NotBefore.ToUniversalTime()
            $resultObject.ValidUntil = $resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.ExpirationTime.ToLocalTime().IsDaylightSavingTime() )
            $resultObject.ValidFrom = $resultObject.AccessTokenInfo.NotBefore.ToLocalTime().AddHours( [int]$resultObject.AccessTokenInfo.NotBefore.ToLocalTime().IsDaylightSavingTime() )
        }

        # Checking if token is valid
        # ToDo implement "validating token information" -> https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#validating-tokens
        if ($resultObject.IsValid) {
            if ($Register) {
                $script:msgraph_Token = $resultObject
                if ($PassThru) { $resultObject }
            } else {
                $resultObject
            }
        } else {
            Stop-PSFFunction -Message "Token failure. Acquired token is not valid" -EnableException $true -Tag "Authorization"
        }
    }

    end {
    }
}

function Add-MgaMailAttachment {
    <#
    .SYNOPSIS
        Add attachment(s) to a draft message in Exchange Online using the graph api.
 
    .DESCRIPTION
        Add attachment(s) to a draft message in Exchange Online using the graph api.
 
        Currently, only file attachments are supportet.
 
    .PARAMETER Message
        Carrier object for Pipeline input.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER File
        The path to the file to add as attachment.
 
    .PARAMETER Link
        The ReferenceAttachment (aka "modern attachment", aka OneDriveLink) to add to the message..
 
    .PARAMETER Item
        The Outlook item to add as attachment.
 
    .PARAMETER Force
        Enforce adding attachment, even when the message is not in draft mode.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/message-post-attachments?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> $mail | Add-MgaMailAttachment -Path "logfile.txt"
 
        Add "logfile.txt" as attachment to message(s) in the variable $mail,
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Drafts -ResultSize 1
 
    .EXAMPLE
        PS C:\> $mail | Add-MgaMailAttachment -Link $ReferenceAttachment
 
        Add a modern attachment as attachment (reference link) to message(s) in the variable $mail,
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Drafts -ResultSize 1
 
        The variable $ReferenceAttachment has to be a object [MSGraph.Exchange.Attachment.ReferenceAttachment]
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'FileAttachment')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('InputObject', 'Id', 'Mail', 'MailMessage', 'MessageId', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Mandatory = $true, ParameterSetName = 'FileAttachment')]
        [Alias('Path', 'FileName', 'FilePath')]
        [string[]]
        $File,

        [Parameter(Mandatory = $true, ParameterSetName = 'ReferenceAttachment')]
        [Alias('ReferenceAttachment', 'LinkPath', 'Uri', 'Url')]
        [MSGraph.Exchange.Attachment.ReferenceAttachment[]]
        $Link,

        [Parameter(Mandatory = $true, ParameterSetName = 'ItemAttachment')]
        [Alias('Event', 'OutlookItem')]
        [psobject[]]
        $Item,

        [switch]
        $Force,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        switch ($PSCmdlet.ParameterSetName) {
            'FileAttachment' {
                $filesToAttach = @()
                foreach ($filePath in $File) {
                    try {
                        $fileItem = Get-ChildItem -Path $filePath -File -ErrorAction Stop
                        $fileItem | Add-Member -MemberType NoteProperty -Name contentBytes -Value ( [System.Convert]::ToBase64String( [System.IO.File]::ReadAllBytes($fileItem.FullName) ) )
                        $filesToAttach = $filesToAttach + $fileItem
                    } catch {
                        Stop-PSFFunction -Message "Specified path '$($filePath)' is invalid or not a file. Please specify a valid file." -EnableException $true -Exception $errorvariable.Exception -Category InvalidData -Tag "Attachment"
                    }
                }
                $namesFileToAttach = "'$([string]::Join("', '",$filesToAttach.Name))'"
            }

            'ReferenceAttachment' {
                # ToDo implemented convinient parsing for referenceAttachments
                $namesFileToAttach = "'$([string]::Join("', '",$Link.Name))'"
            }

            'ItemAttachment' {
                # ToDo implemented adding item attachment
                Stop-PSFFunction -Message "adding item attachment is not implemented, yet."
                foreach ($itemObject in $Item) {
                }
            }

            Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
        }
    }

    process {
        foreach ($messageItem in $Message) {
            Write-PSFMessage -Level Debug -Message "Adding attachment(s) $($namesFileToAttach) to message '$($messageItem)' by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            if (-not $messageItem.InputObject.IsDraft -and (-not $Force)) {
                if ($PSCmdlet.ShouldContinue("The mesaage is not a draft message! Would you really like to add attachment(s) $($namesFileToAttach) to message '$($messageItem)'?", "$($messageItem) is not a draft message") ) {
                    Write-PSFMessage -Level Verbose -Message "Confirmation specified to add attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce"
                } else {
                    Write-PSFMessage -Level Important -Message "Abort adding attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce"
                    return
                }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            # prepare parameters for rest call
            $invokeParam = @{
                "Field"        = "messages/$($messageItem.Id)/attachments"
                "Token"        = $Token
                "User"         = $User
                "ApiVersion"   = "beta"
                "FunctionName" = $MyInvocation.MyCommand
            }

            $data = @()
            switch ($PSCmdlet.ParameterSetName) {
                'FileAttachment' {
                    foreach ($fileToAttach in $filesToAttach) {
                        # prepare REST Body
                        $bodyJSON = New-JsonAttachmentObject -Name $fileToAttach.Name -Size $fileToAttach.Length -IsInline $false -contentBytes $fileToAttach.contentBytes -FunctionName $MyInvocation.MyCommand
                        $invokeParam.Add("Body", $bodyJSON)

                        # add attachment
                        if ($pscmdlet.ShouldProcess("Message '$($messageItem)'", "Add FileAttachment '$($fileToAttach.FullName)'")) {
                            Write-PSFMessage -Level Verbose -Message "Add '$($fileToAttach.FullName)' to message '$($messageItem)'" -Tag "AddData"
                            $data = $data + (Invoke-MgaRestMethodPost @invokeParam)
                        }
                        $invokeParam.Remove("Body")
                    }
                }

                'ReferenceAttachment' {
                    foreach ($linkItem in $Link) {
                        # prepare REST Body
                        $bodyJSON = New-JsonAttachmentObject -SourceUrl $linkItem.SourceUrl -Name $linkItem.Name -ProviderType $linkItem.ProviderType -IsFolder $linkItem.IsFolder -Permission $linkItem.Permission -FunctionName $MyInvocation.MyCommand
                        $invokeParam.Add("Body", $bodyJSON)

                        # add attachment
                        if ($pscmdlet.ShouldProcess("Message '$($messageItem)'", "Add ReferenceAttachment '$($linkItem.Name)'")) {
                            Write-PSFMessage -Level Verbose -Message "Getting '$($linkItem.ToString())' as ReferenceAttachment to message '$($messageItem)'" -Tag "AddData"
                            $data = $data + (Invoke-MgaRestMethodPost @invokeParam)
                        }
                        $invokeParam.Remove("Body")
                    }
                }

                'ItemAttachment' {
                    # ToDo implemented adding item attachment
                    foreach ($itemObject in $Item) {
                    }
                }

                Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
            }

            #region output data
            foreach ($output in $data) {
                if ($PassThru) {
                    $AttachmentObject = New-MgaAttachmentObject -RestData $output -ParentObject $messageItem.InputObject -ApiVersion "beta" -ResultSize $ResultSize -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                    $AttachmentObject
                }
            }
            #endregion output data
        }
    }

    end {
    }
}

function Export-MgaMailAttachment {
    <#
    .SYNOPSIS
        Export a mail attachment to a file
 
    .DESCRIPTION
        Export/saves a mail attachment to a file
 
    .PARAMETER Attachment
        The attachment object to export
 
    .PARAMETER Path
        The directory where to export the attachment
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> Export-MgaMailAttachment -Attachment $attachment -Path "$HOME"
 
        Export the attement to the users profile base directory
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [Alias('Save-MgaMailAttachment')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)]
        [Alias('InputObject', 'AttachmentId', 'Id')]
        [MSGraph.Exchange.Attachment.AttachmentParameter[]]
        $Attachment,

        [String]
        $Path = (Get-Location).Path,

        [switch]
        $PassThru
    )
    begin {
        if (Test-Path -Path $Path -IsValid) {
            if (-not (Test-Path -Path $Path -PathType Container)) {
                Stop-PSFFunction -Message "Specified path is a file and not a path. Please specify a directory." -EnableException $true -Category "InvalidPath" -Tag "Attachment"
            }
        } else {
            Stop-PSFFunction -Message "Specified path is not valid. Please specify a valid directory." -EnableException $true -Category "InvalidPath" -Tag "Attachment"
        }
        $Path = Resolve-Path -Path $Path
    }

    process {
        foreach ($attachmentItem in $Attachment) {
            #switching between different types to export
            switch ($attachmentItem.TypeName) {
                "MSGraph.Exchange.Attachment.FileAttachment" {
                    if ($pscmdlet.ShouldProcess($attachmentItem, "Export to $($Path.Path)")) {
                        Write-PSFMessage -Level Verbose -Message "Exporting attachment '$($attachmentItem)' to '$($Path.ToString())'" -Tag "ExportData"
                        $attachmentItem.InputObject.ContentBytes | Set-Content -Path (Join-Path -Path $Path -ChildPath $attachmentItem.Name) -Encoding Byte
                    }
                }

                "MSGraph.Exchange.Attachment.ItemAttachment" {
                    if ($pscmdlet.ShouldProcess($attachmentItem, "Export to $($Path.Path)")) {
                        Write-PSFMessage -Level Important -Message "Export of $($attachmentItem.TypeName) is not implemented, yet. Could not export '$($attachmentItem.InputObject)'" -Tag "ExportData"
                    }
                }

                "MSGraph.Exchange.Attachment.ReferenceAttachment" {
                    if ($pscmdlet.ShouldProcess($attachmentItem, "Export to $($Path.Path)")) {
                        $shell = New-Object -ComObject ("WScript.Shell")
                        $shortCut = $shell.CreateShortcut("$($Path.Path)\$($attachmentItem.InputObject.Name).lnk")
                        $shortCut.TargetPath = $attachmentItem.InputObject.SourceUrl
                        $shortCut.Save()
                    }
                }

                "MSGraph.Exchange.Attachment.Attachment" {
                    Write-PSFMessage -Level Warning -Message "$($attachmentItem) is not a exportable attachment." -Tag "ParameterSetHandling"
                }

                Default {
                    Write-PSFMessage -Level Warning -Message "$($attachmentItem) is not a exportable attachment." -Tag "ParameterSetHandling"
                    continue
                }
            }

            if ($PassThru) { $attachmentItem }
        }
    }

    end {
    }
}

function Get-MgaMailAttachment {
    <#
    .SYNOPSIS
        Retrieves the attachment object from a email message in Exchange Online using the graph api.
 
    .DESCRIPTION
        Retrieves the attachment object from a email message in Exchange Online using the graph api.
 
    .PARAMETER Message
        Carrier object for Pipeline input.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER Name
        The name to filter by.
        (Client Side filtering)
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER IncludeInlineAttachment
        This will retrieve also attachments like pictures in the html body of the mail.
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage | Get-MgaMailAttachment
 
        Return all emails attachments from all mails in the inbox of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage | Get-MgaMailAttachment -Name "MyName*"
 
        Return all emails attachments with name MyName* from all mails in the inbox of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage | Get-MgaMailAttachment -IncludeInlineAttachment
 
        Return also "inline" attachments, like pictures in html mails from all emails in the inbox of the user connected to through a token.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('InputObject', 'Id', 'Mail', 'MailMessage', 'MessageId', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Position = 1)]
        [Alias('Filter', 'NameFilter')]
        [string]
        $Name = "*",

        [switch]
        $IncludeInlineAttachment,

        [string]
        $User,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Mail.Read"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            #region query data
            $invokeParam = @{
                "Field"        = "messages/$($messageItem.Id)/attachments"
                "Token"        = $Token
                "User"         = $User
                "ResultSize"   = $ResultSize
                "ApiVersion"   = "beta"
                "FunctionName" = $MyInvocation.MyCommand
            }

            Write-PSFMessage -Level Verbose -Message "Getting attachment from message '$($messageItem)'" -Tag "QueryData"
            $data = Invoke-MgaRestMethodGet @invokeParam | Where-Object { $_.name -like $Name }
            if (-not $IncludeInlineAttachment) { $data = $data | Where-Object isInline -eq $false }
            #endregion query data

            #region output data
            foreach ($output in $data) {
                $AttachmentObject = New-MgaAttachmentObject -RestData $output -ParentObject $messageItem.InputObject -ApiVersion "beta" -ResultSize $ResultSize -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                $AttachmentObject
            }
            #endregion output data
        }
    }

    end {
    }
}

function Remove-MgaMailAttachment {
    <#
    .SYNOPSIS
        Remove attachment(s) from a email message(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Remove attachment(s) from a email message(s) in Exchange Online using the graph api.
 
    .PARAMETER Message
        Carrier object for Pipeline input.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER Attachment
        Carrier object for Pipeline input.
        This can be the id of the attachment or a attachment object passed in.
 
    .PARAMETER Name
        The name of the attachment to delete.
 
    .PARAMETER IncludeInlineAttachment
        Also search and remove InlineAttachment.
        Per default, only attachments outside a html body are recognized.
 
    .PARAMETER Force
        Suppress any confirmation request and enforce removing attachment on any kind of message.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the object to the console.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/attachment-delete?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage -Folder Drafts | Remove-MgaMailAttachment
 
        Delete attachment(s) from all mails in drafts folder of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage -Folder Drafts | Remove-MgaMailAttachment -Name "MyName*"
 
        Delete attachment(s) with name MyName* from all mails in drafts folder of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage -Folder Drafts | Remove-MgaMailAttachment -IncludeInlineAttachment
 
        Delete also "inline" attachments, like pictures in html mails from all emails in drafts folder of the user connected to through a token.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'MessageInputObject')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'MessageInput', Position = 0)]
        [Alias('Mail', 'MailMessage', 'MessageId', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'AttachmentInput', Position = 0)]
        [Alias('Attachments', 'AttachmentId', 'AttachmentObject')]
        [MSGraph.Exchange.Attachment.AttachmentParameter[]]
        $Attachment,

        [Parameter(ParameterSetName = 'MessageInput', Position = 1)]
        [Alias('Filter', 'NameFilter')]
        [string]
        $Name = "*",

        [switch]
        $IncludeInlineAttachment,

        [switch]
        $Force,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        if ($PSCmdlet.ParameterSetName -like 'MessageInput') {
            $Attachment = @()
            foreach ($messageItem in $Message) {
                #region checking input object type and query message if required
                if ($messageItem.TypeName -like "System.String") {
                    $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                    if (-not $messageItem) { continue }
                }

                if (-not $messageItem.InputObject.IsDraft -and (-not $Force)) {
                    if ($PSCmdlet.ShouldContinue("The mesaage is not a draft message! Would you really like to add attachment(s) $($namesFileToAttach) to message '$($messageItem)'?", "$($messageItem) is not a draft message") ) {
                        Write-PSFMessage -Level Verbose -Message "Confirmation specified to add attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce"
                    } else {
                        Write-PSFMessage -Level Important -Message "Abort adding attachment(s) to non draft message '$($messageItem)'" -Tag "AddAttachmentEnforce"
                        return
                    }
                }

                $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
                #endregion checking input object type and query message if required

                $getAttachmentParam = @{
                    "Message" = $messageItem
                    "Name"    = $Name
                    "User"    = $User
                    "Token"   = $Token
                }
                if ($IncludeInlineAttachment) { $getAttachmentParam.Add("IncludeInlineAttachment", $true) }
                $output = (Get-MgaMailAttachment @getAttachmentParam | Where-Object { $_.name -like $Name })
                if ($output) {
                    foreach ($outputItem in $output) {
                        $Attachment = $Attachment + [MSGraph.Exchange.Attachment.AttachmentParameter]$outputItem
                    }
                }
            }
            if (-not $Attachment) {
                Write-PSFMessage -Level Important -Message "Nothing found to delete." -Tag "QueryData"
            }
        }

        foreach ($attachmentItem in $Attachment) {
            Write-PSFMessage -Level Debug -Message "Deleting attachment '$($attachmentItem)' from message '$($attachmentItem.InputObject.ParentObject.Name)' by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

            # prepare parameters for rest call
            $invokeParam = @{
                "Field"        = "messages/$($attachmentItem.InputObject.ParentObject.Id)/attachments/$($attachmentItem.id)"
                "Token"        = $Token
                "User"         = $User
                "Confirm"      = $false
                "FunctionName" = $MyInvocation.MyCommand
            }

            # remove attachment
            if ($Force) {
                $proceed = $true
            } else {
                $proceed = $pscmdlet.ShouldProcess("Message '$($attachmentItem.InputObject.ParentObject.Name)'", "Delete attachment '$($attachmentItem)'")
            }
            if ($proceed) {
                Write-PSFMessage -Level Verbose -Message "Delete attachment '$($attachmentItem)' from message '$($attachmentItem.InputObject.ParentObject.Name)'" -Tag "RemoveData"
                Invoke-MgaRestMethodDelete @invokeParam
            }

            #region passthru data
            if ($PassThru) { $attachmentItem.InputObject }
            #endregion passthru data
        }
    }

    end {
    }
}

function Get-MgaExchCategory {
    <#
    .SYNOPSIS
        Retrieves categories in Exchange Online using the graph api.
 
    .DESCRIPTION
        Retrieves categories in Exchange Online using the graph api.
 
    .PARAMETER InputObject
        Carrier object for Pipeline input.Accepts CategoryObjects and strings.
 
    .PARAMETER Id
        The Id to filter by.
        (Client Side filtering)
 
    .PARAMETER Name
        The name to filter by.
        (Client Side filtering)
 
    .PARAMETER Color
        The color to filter by.
        (Client Side filtering)
 
        Tab completion is available on this parameter for the list of the 25 predefined colors.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory
 
        Return all categories of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory -Id "89101089-690d-4263-9470-b674e709a996"
 
        Return the category with the specified Id of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory -Name "*category"
 
        Return all categories with names like "*category" of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory -Color "Blue"
 
        Return all categories with names like "*category" of the user connected to through a token.
 
    #>

    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.Exchange.Category.OutlookCategory])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'ByInputOBject')]
        [Alias('Category')]
        [MSGraph.Exchange.Category.CategoryParameter[]]
        $InputObject,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ById')]
        [Alias('IdFilter', 'FilterId')]
        [guid[]]
        $Id,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByName')]
        [Alias('NameFilter', 'FilterName')]
        [string[]]
        $Name,

        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'ByColor')]
        [Alias('ColorFilter', 'FilterColor')]
        [MSGraph.Exchange.Category.ColorName[]]
        $Color,

        [string]
        $User,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )
    begin {
        $requiredPermission = "MailboxSettings.Read"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        #region query data
        $invokeParam = @{
            "Token"        = $Token
            "User"         = $User
            "ResultSize"   = $ResultSize
            "FunctionName" = $MyInvocation.MyCommand
        }

        $data = @()
        if ($PSCmdlet.ParameterSetName -like 'ByInputOBject') {
            foreach ($categoryItem in $InputObject) {
                #region checking input object type and query message if required
                if ($categoryItem.TypeName -like "System.String") {
                    $categoryItem = Resolve-MailObjectFromString -Object $categoryItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                    if (-not $categoryItem) { continue }
                }

                $User = Resolve-UserInMailObject -Object $categoryItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
                #endregion checking input object type and query message if required

                $invokeParam.Add("Field", "outlook/masterCategories/$($categoryItem.Id)")
                Write-PSFMessage -Level Verbose -Message "Get refresh on category '$($categoryItem)'" -Tag "QueryData"
                $data = $data + (Invoke-MgaRestMethodGet @invokeParam)
                $invokeParam.Remove("Field")
            }
        } else {
            $invokeParam.Add("Field", "outlook/masterCategories")
            Write-PSFMessage -Level Verbose -Message "Getting available categories" -Tag "QueryData"
            $data = $data + (Invoke-MgaRestMethodGet @invokeParam)
        }
        #endregion query data

        #region filter data
        switch ($PSCmdlet.ParameterSetName) {
            'ById' {
                $data = foreach ($filter in $Id) {
                    Write-PSFMessage -Level VeryVerbose -Message "Filtering on id '$($filter)'." -Tag "FilterData"
                    $data | Where-Object Id -like $filter.Guid
                }
            }
            'ByName' {
                $data = foreach ($filter in $Name) {
                    Write-PSFMessage -Level VeryVerbose -Message "Filtering on name '$($filter)'." -Tag "FilterData"
                    $data | Where-Object displayname -like $filter
                }
            }
            'ByColor' {
                $data = foreach ($filter in $Color) {
                    Write-PSFMessage -Level VeryVerbose -Message "Filtering on color '$($filter)'." -Tag "FilterData"
                    $data | Where-Object Color -like ([MSGraph.Exchange.Category.OutlookCategory]::Parse($filter))
                }
            }
            Default {}
        }
        #endregion filter data

        #region output data
        Write-PSFMessage -Level VeryVerbose -Message "Output $( ($data | Measure-Object).Count ) objects." -Tag "OutputData"
        foreach ($output in $data) {
            if ($output.User) { $User = $output.User }
            $categoryObject = [MSGraph.Exchange.Category.OutlookCategory]::new( $output.id, $output.displayName, $output.color, $User, $output)
            Write-PSFMessage -Level Debug -Message "Output new object '$($categoryObject)'." -Tag "OutputData"
            $categoryObject
        }
        #endregion output data
    }

    end {
    }
}

function New-MgaExchCategory {
    <#
    .SYNOPSIS
        Creates a new category in Exchange Online using the graph api.
 
    .DESCRIPTION
        Creates a new category in Exchange Online using the graph api.
 
    .PARAMETER Name
        The category name.
 
    .PARAMETER Color
        The color for the category.
 
        Tab completion is available on this parameter for the list of the 25 predefined colors.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/outlookuser-post-mastercategories?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> New-MgaExchCategory -Name "Important stuff"
 
        Creates a category "Important stuff" in the mailbox of the user connected to through a token.
        The new category will creates without color mapping.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory -Name "Important stuff" -Color "Blue"
 
        Creates a blue colored category "Important stuff" in the mailbox of the user connected to through a token.
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([MSGraph.Exchange.Category.OutlookCategory])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('DisplayName', 'Category', 'InputObject')]
        [string[]]
        $Name,

        [Parameter(Mandatory = $false, Position = 1)]
        [Alias('ColorName')]
        [MSGraph.Exchange.Category.ColorName]
        $Color,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token
    )
    begin {
        $requiredPermission = "MailboxSettings.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        if ($Color) {
            [String]$colorValue = [MSGraph.Exchange.Category.OutlookCategory]::Parse($Color)
        } else {
            [String]$colorValue = [MSGraph.Exchange.Category.ColorKey]::None
        }
    }

    process {
        foreach ($categoryName in $Name) {
            Write-PSFMessage -Level Verbose -Message "Create new category '$($categoryName)'" -Tag "CreateData"

            #region prepare rest call to create data
            $bodyJSON = @{
                displayName = $categoryName
                color       = $colorValue
            } | ConvertTo-Json

            $invokeParam = @{
                "Field"        = "outlook/masterCategories"
                "Body"         = $bodyJSON
                "Token"        = $Token
                "User"         = $User
                "FunctionName" = $MyInvocation.MyCommand
            }
            #endregion prepare rest call to create data

            # create data
            if ($pscmdlet.ShouldProcess($categoryName, "Create")) {
                $data = Invoke-MgaRestMethodPost @invokeParam
            }

            #region output data
            foreach ($output in $data) {
                if ($output.User) { $User = $output.User }
                $categoryObject = [MSGraph.Exchange.Category.OutlookCategory]::new( $output.id, $output.displayName, $output.color, $User, $output)
                $categoryObject
            }
            #endregion output data
        }
    }

    end {
    }
}

function Remove-MgaExchCategory {
    <#
    .SYNOPSIS
        Remove a category in Exchange Online using the graph api.
 
    .DESCRIPTION
        Remove a category in Exchange Online using the graph api.
 
    .PARAMETER InputObject
        Carrier object for Pipeline input.Accepts CategoryObjects and strings.
 
    .PARAMETER Force
        Suppress any confirmation request and enforce removing.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the modified category to the console.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/outlookcategory-delete?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> Remove-MgaExchCategory -Name "Important stuff"
 
        Remove existing category "Important stuff" in the mailbox of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory -Name "Important stuff" | Remove-MgaExchCategory -Force
 
        Remove existing category "Important stuff" WITHOUT CONFIRMATION in the mailbox of the user connected to through a token.
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([MSGraph.Exchange.Category.OutlookCategory])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('Name', 'DisplayName', 'Category')]
        [MSGraph.Exchange.Category.CategoryParameter[]]
        $InputObject,

        [switch]
        $Force,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "MailboxSettings.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        foreach ($categoryItem in $InputObject) {
            Write-PSFMessage -Level Verbose -Message "Working on removal of category '$($categoryItem)'" -Tag "QueryData"

            #region checking input object type and query message if required
            if ($categoryItem.TypeName -like "System.String") {
                $categoryItem = Resolve-MailObjectFromString -Object $categoryItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                if (-not $categoryItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $categoryItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            #region prepare rest call to create data

            $invokeParam = @{
                "Field"        = "outlook/masterCategories/$($categoryItem.Id)"
                "Token"        = $Token
                "User"         = $User
                "Confirm"      = $false
                "FunctionName" = $MyInvocation.MyCommand
            }
            #endregion prepare rest call to create data

            # set data
            if ($Force) {
                $proceed = $true
            } else {
                $proceed = $pscmdlet.ShouldProcess($categoryItem.Name, "Delete")
            }
            if ($proceed) {
                Write-PSFMessage -Level Verbose -Message "Delete category '$($categoryItem)'." -Tag "RemoveData"
                Invoke-MgaRestMethodDelete @invokeParam
            }

            #region output data
            if ($PassThru) { $categoryItem.InputObject }
            #endregion output data
        }
    }

    end {
    }
}

function Set-MgaExchCategory {
    <#
    .SYNOPSIS
        Set a category in Exchange Online using the graph api.
 
    .DESCRIPTION
        Set a category in Exchange Online using the graph api.
 
    .PARAMETER InputObject
        Carrier object for Pipeline input.Accepts CategoryObjects and strings.
 
    .PARAMETER Color
        The color for the category.
 
        Tab completion is available on this parameter for the list of the 25 predefined colors.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the modified category to the console.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/outlookcategory-update?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> Set-MgaExchCategory -Name "Important stuff" -Color Black
 
        Set color "black" on existing category "Important stuff" in the mailbox of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> Get-MgaExchCategory -Name "Important stuff" | Set-MgaExchCategory -Color "Blue"
 
        Set color "blue" on existing category "Important stuff" in the mailbox of the user connected to through a token.
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([MSGraph.Exchange.Category.OutlookCategory])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('Name', 'DisplayName', 'Category')]
        [MSGraph.Exchange.Category.CategoryParameter[]]
        $InputObject,

        <# Currently not available as writeable property on microsoft graph version 1.0 and beta
        [Parameter(Mandatory = $false)]
        [string]
        $NewName,
#>

        [Parameter(Mandatory = $false)]
        [Alias('ColorName')]
        [MSGraph.Exchange.Category.ColorName]
        $Color,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "MailboxSettings.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        foreach ($categoryItem in $InputObject) {
            #region checking input object type and query message if required
            if ($categoryItem.TypeName -like "System.String") {
                $categoryItem = Resolve-MailObjectFromString -Object $categoryItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                if (-not $categoryItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $categoryItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            #region prepare rest call to create data
            $bodyJSON = @{}
            $boundParameters = @()
            if ($NewName) {
                $boundParameters = $boundParameters + "NewName"
                $bodyJSON.Add("displayName", $NewName)
            }
            if ($Color) {
                $boundParameters = $boundParameters + "Color"
                [String]$colorValue = [MSGraph.Exchange.Category.OutlookCategory]::Parse($Color)
                $bodyJSON.Add("color", $colorValue.ToLower())
            }
            $bodyJSON = $bodyJSON | ConvertTo-Json

            $invokeParam = @{
                "Field"        = "outlook/masterCategories/$($categoryItem.Id)"
                "Body"         = $bodyJSON
                "Token"        = $Token
                "User"         = $User
                "FunctionName" = $MyInvocation.MyCommand
            }
            #endregion prepare rest call to create data
            Write-PSFMessage -Level Verbose -Message "Set property '$([string]::Join("', '", $boundParameters))' on category '$($categoryItem)'" -Tag "SetData"

            # set data
            if ($pscmdlet.ShouldProcess($categoryItem, "Set property '$([string]::Join("', '", $boundParameters))'")) {
                $data = Invoke-MgaRestMethodPatch @invokeParam
            }

            #region output data
            if ($PassThru) {
                foreach ($output in $data) {
                    if ($output.User) { $User = $output.User }
                    $categoryObject = [MSGraph.Exchange.Category.OutlookCategory]::new( $output.id, $output.displayName, $output.color, $User, $output)
                    $categoryObject
                }
            }
            #endregion output data
        }
    }

    end {
    }
}

function Get-MgaMailFolder {
    <#
    .SYNOPSIS
        Get mail folder(s) in Exchange Online
 
    .DESCRIPTION
        Get mail folder(s) with metadata from Exchange Online via Microsoft Graph API
 
    .PARAMETER Name
        The name of the folder(S) to query.
 
    .PARAMETER IncludeChildFolders
        Output all subfolders on queried folder(s).
 
    .PARAMETER Recurse
        Iterates through the whole folder structure and query all subfolders.
 
    .PARAMETER Filter
        The name to filter by.
        (Client Side filtering)
 
        Try to avoid, when filtering on single name, use parameter -Name instead of -Filter.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaMailFolder
 
        Returns all folders in the mailbox of the connected user.
 
    .EXAMPLE
        PS C:\> Get-MgaMailFolder -Name Inbox
 
        Returns the "wellknown" inbox folder in the mailbox of the connected user.
        The wellknown folders can be specified by tab completion.
 
    .EXAMPLE
        PS C:\> Get-MgaMailFolder -Name Inbox -IncludeChildFolders
 
        Returns inbox and the next level of subfolders in the inbox of the connected user.
 
    .EXAMPLE
        PS C:\> Get-MgaMailFolder -Name Inbox -Recurse
 
        Returns inbox and the all subfolders underneath the inbox of the connected user.
        This one is like the "-Recurse" switch on the dir/Get-ChildItem command.
 
    .EXAMPLE
        PS C:\> Get-MgaMailFolder -Filter "My*" -User "max.master@contoso.onmicrosoft.com" -Token $Token
 
        Retrieves all folders where name starts with My in the mailbox of "max.master@contoso.onmicrosoft.com", using the connection token stored in $Token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailFolder -ResultSize 5
 
        Retrieves only the first 5 folders in the mailbox of the connected user.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.Exchange.Mail.Folder])]
    param (
        [Parameter(ParameterSetName = 'ByFolderName', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)]
        [Alias('FolderName', 'InputObject', 'DisplayName', 'Id')]
        [MSGraph.Exchange.Mail.FolderParameter[]]
        $Name,

        [switch]
        $IncludeChildFolders,

        [switch]
        $Recurse,

        [string]
        $Filter = "*",

        [string]
        $User,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Mail.Read"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        if ($Recurse) { $IncludeChildFolders = $true }

        #region helper subfunctions
        function invoke-internalMgaGetMethod ($invokeParam, [int]$level, [MSGraph.Exchange.Mail.Folder]$parentFolder, [String]$FunctionName) {
            # Subfunction for query objects and creating valid new objects from the query result
            $folderData = Invoke-MgaRestMethodGet @invokeParam
            foreach ($folderOutput in $folderData) {
                New-MgaMailFolderObject -RestData $folderOutput -ParentFolder $parentFolder -Level $level #-FunctionName $FunctionName
            }
        }

        function get-childfolder ($output, [int]$level, $invokeParam) {
            $FoldersWithChilds = $output | Where-Object ChildFolderCount -gt 0
            $childFolders = @()

            do {
                $level = $level + 1
                foreach ($folderItem in $FoldersWithChilds) {
                    if ($folderItem.ChildFolderCount -gt 0) {
                        Write-PSFMessage -Level VeryVerbose -Message "Getting childfolders for folder '$($folderItem.Name)'" -Tag "QueryData"
                        $invokeParam.Field = "mailFolders/$($folderItem.Id)/childFolders"
                        $childFolderOutput = invoke-internalMgaGetMethod -invokeParam $invokeParam -level $level -parentFolder $folderItem -FunctionName $MyInvocation.MyCommand

                        $FoldersWithChilds = $childFolderOutput | Where-Object ChildFolderCount -gt 0
                        $childFolders = $childFolders + $childFolderOutput
                    }
                }
            } while ($Recurse -and $FoldersWithChilds)

            $childFolders
        }
        #endregion helper subfunctions
    }

    process {
        Write-PSFMessage -Level VeryVerbose -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        switch ($PSCmdlet.ParameterSetName) {
            "Default" {
                $baseLevel = 1
                $invokeParam = @{
                    "Field"        = 'mailFolders'
                    "Token"        = $Token
                    "User"         = Resolve-UserString -User $User
                    "ResultSize"   = $ResultSize
                    "FunctionName" = $MyInvocation.MyCommand
                }

                $output = invoke-internalMgaGetMethod -invokeParam $invokeParam -level $baseLevel -FunctionName $MyInvocation.MyCommand  | Where-Object displayName -Like $Filter

                if ($output -and $IncludeChildFolders) {
                    $childFolders = $output | Where-Object ChildFolderCount -gt 0 | ForEach-Object {
                        get-childfolder -output $_ -level $baseLevel -invokeParam $invokeParam
                    }
                    if ($childFolders) {
                        [array]$output = [array]$output + $childFolders
                    }
                }

                if (-not $output) {
                    Stop-PSFFunction -Message "Unexpected error. Could not query root folders from user '$($User)'." -Tag "QueryData" -EnableException $true
                }
            }

            "ByFolderName" {
                foreach ($folder in $Name) {
                    $baseLevel = 1
                    Write-PSFMessage -Level VeryVerbose -Message "Getting folder '$( if($folder.Name){$folder.Name}else{$folder.Id} )'" -Tag "ParameterSetHandling"
                    $invokeParam = @{
                        "Token"        = $Token
                        "User"         = Resolve-UserString -User $User
                        "ResultSize"   = $ResultSize
                        "FunctionName" = $MyInvocation.MyCommand
                    }
                    if ($folder.id) {
                        $invokeParam.add("Field", "mailFolders/$($folder.Id)")
                    } else {
                        $invokeParam.add("Field", "mailFolders?`$filter=DisplayName eq '$($folder.Name)'")
                    }

                    $output = invoke-internalMgaGetMethod -invokeParam $invokeParam -level $baseLevel -FunctionName $MyInvocation.MyCommand | Where-Object displayName -Like $Filter

                    if ($output -and $IncludeChildFolders) {
                        $childFolders = get-childfolder -output $output -level $baseLevel -invokeParam $invokeParam
                        if ($childFolders) {
                            [array]$output = [array]$output + $childFolders
                        }
                    }

                    if (-not $output) {
                        Write-PSFMessage -Level Warning -Message "Folder '$($folder)' not found." -Tag "QueryData"
                    }
                }
            }

            Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
        }

        $output
    }

    end {
    }
}

function Move-MgaMailFolder {
    <#
    .SYNOPSIS
        Move folder(s) to another folder
 
    .DESCRIPTION
        Move folder(s) to another folder in Exchange Online using the graph api.
 
    .PARAMETER Folder
        Carrier object for Pipeline input. Accepts folders and strings.
 
    .PARAMETER DestinationFolder
        The destination folder where to move the folder to.
 
        Tab completion is available on this parameter for a list of well known folders.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational folders will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> Move-MgaMailFolder -Folder $folder -DestinationFolder $destinationFolder
 
        Moves the folder(s) in variable $folder to the folder in the variable $destinationFolder.
        also possible:
        PS C:\> $folder | Move-MgaMailFolder -DestinationFolder $destinationFolder
 
        The variable $folder can be represent:
        PS C:\> $folder = Get-MgaMailFolder -Name "MyFolder"
 
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    .EXAMPLE
        PS C:\> Move-MgaMailFolder -Id $folder.id -DestinationFolder $destinationFolder.id
 
        Moves folders into the folder $destinationFolder.
 
        The variable $folder can be represent:
        PS C:\> $folder = Get-MgaMailFolder -Name "MyFolder"
 
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [Alias()]
    [OutputType([MSGraph.Exchange.Mail.Folder])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('InputObject', 'FolderId', 'Id')]
        [MSGraph.Exchange.Mail.FolderParameter[]]
        $Folder,

        [Parameter(Mandatory = $true, Position = 1)]
        [Alias('DestinationObject', 'DestinationFolderId')]
        [MSGraph.Exchange.Mail.FolderParameter]
        $DestinationFolder,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region checking DestinationFolder and query folder if required
        if ($DestinationFolder.TypeName -like "System.String") {
            $DestinationFolder = Resolve-MailObjectFromString -Object $DestinationFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
            if (-not $DestinationFolder) { throw }
        }

        $User = Resolve-UserInMailObject -Object $DestinationFolder -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
        #endregion checking DestinationFolder and query folder if required

        $bodyJSON = @{
            destinationId = $DestinationFolder.Id
        } | ConvertTo-Json
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($folderItem in $Folder) {
            #region checking input object type and query folder if required
            if ($folderItem.TypeName -like "System.String") {
                $folderItem = Resolve-MailObjectFromString -Object $folderItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                if (-not $folderItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $folderItem -User $User -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($pscmdlet.ShouldProcess("Folder '$($folderItem)'", "Move to '$($DestinationFolder)'")) {
                Write-PSFMessage -Tag "FolderUpdate" -Level Verbose -Message "Move folder '$($folderItem)' into folder '$($DestinationFolder)'"
                $invokeParam = @{
                    "Field"        = "mailFolders/$($folderItem.Id)/move"
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $output = Invoke-MgaRestMethodPost @invokeParam
                if ($PassThru) {
                    New-MgaMailFolderObject -RestData $output -ParentFolder $DestinationFolder.InputObject -FunctionName $MyInvocation.MyCommand
                }
            }
        }
    }

}

function New-MgaMailFolder {
    <#
    .SYNOPSIS
        Creates a folder in Exchange Online using the graph api.
 
    .DESCRIPTION
        Creates a new folder in Exchange Online using the graph api.
 
    .PARAMETER Name
        The name to be set as new name.
 
    .PARAMETER ParentFolder
        The folder where the new folder should be created in. Do not specify to create
        a folder on the root level.
 
        Possible values are a valid folder Id or a Mga folder object passed in.
        Tab completion is available on this parameter for a list of well known folders.
 
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> New-MgaMailFolder -Name 'MyFolder'
 
        Creates a new folder named "MyFolder" on the root level of the mailbox
 
    .EXAMPLE
        PS C:\> New-MgaMailFolder -Name 'MyFolder' -ParentFolder $folder
 
        Creates a new folder named "MyFolder" inside the folder passed in with the variable $folder
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.Exchange.Mail.Folder])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FolderName', 'DisplayName')]
        [string[]]
        $Name,

        [Parameter(Mandatory = $false, Position = 1)]
        [Alias('Parent', 'ParentFolderId')]
        [MSGraph.Exchange.Mail.FolderParameter[]]
        $ParentFolder,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region checking input object type and query folder if required
        if ($ParentFolder.TypeName -like "System.String") {
            $ParentFolder = Resolve-MailObjectFromString -Object $ParentFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
            if (-not $ParentFolder) { throw }
        }

        if ($ParentFolder) {
            $User = Resolve-UserInMailObject -Object $ParentFolder -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
        }
        #endregion checking input object type and query message if required
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($NameItem in $Name) {
            if ($pscmdlet.ShouldProcess($NameItem, "New")) {
                $msg = "Creating subfolder '$($NameItem)'"
                if ($ParentFolder) { $msg = $msg + " in '$($ParentFolder)'"}
                Write-PSFMessage -Tag "FolderCreation" -Level Verbose -Message $msg

                $bodyJSON = @{
                    displayName = $NameItem
                } | ConvertTo-Json

                $invokeParam = @{
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                if ($ParentFolder.Id) {
                    $invokeParam.Add("Field", "mailFolders/$($ParentFolder.Id)/childFolders")
                } else {
                    $invokeParam.Add("Field", "mailFolders")
                }

                $output = Invoke-MgaRestMethodPost @invokeParam
                New-MgaMailFolderObject -RestData $output -ParentFolder $ParentFolder.InputObject -FunctionName $MyInvocation.MyCommand
            }
        }
    }

    end {
    }
}

function Remove-MgaMailFolder {
    <#
    .SYNOPSIS
        Remove folder(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Remove folder(s) in Exchange Online using the graph api.
 
        ATTENTION! The command does what it is name to!
        The folder will not be moved to 'deletedObjects', it will be deleted.
 
    .PARAMETER Folder
        The folder to be removed.
        This can be a name of the folder, it can be the Id of the folder or it can be a folder object passed in.
 
        Tab completion is available on this parameter for a list of well known folders.
 
    .PARAMETER Force
        If specified the user will not prompted on confirmation.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> Remove-MgaMailFolder -Name 'MyFolder'
 
        Removes folder named "MyFolder".
        The folder has to be on the root level of the mailbox to be specified by individual name.
 
    .EXAMPLE
        PS C:\> Remove-MgaMailFolder -Name $folder
 
        Removes folder represented by the variable $folder.
        You will be prompted for confirmation.
 
        The variable $folder can be represent:
        PS C:\> $folder = Get-MgaMailFolder -Folder "MyFolder"
 
    .EXAMPLE
        PS C:\> $folder | Remove-MgaMailFolder -Force
 
        Removes folder represented by the variable $folder.
        ATTENTION, There will be NO prompt for confirmation!
 
        The variable $folder can be represent:
        PS C:\> $folder = Get-MgaMailFolder -Folder "MyFolder"
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([MSGraph.Exchange.Mail.Folder])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('FolderName', 'FolderId', 'InputObject', 'DisplayName', 'Name', 'Id')]
        [MSGraph.Exchange.Mail.FolderParameter[]]
        $Folder,

        [switch]
        $Force,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($folderItem in $Folder) {
            #region checking input object type and query folder if required
            if ($folderItem.TypeName -like "System.String") {
                if (($folderItem.IsWellKnownName -and $folderItem.Id -like "recoverableitemsdeletions") -or $folderItem.name -like "recoverableitemsdeletions") {
                    Write-PSFMessage -Level Important -Message "Can not delete well known folder 'recoverableitemsdeletions'. Continue without action on folder."
                    continue
                }
                $folderItem = Resolve-MailObjectFromString -Object $folderItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                if (-not $folderItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $folderItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess($folderItem, "Remove (ATTENTION! Folder will not be moved to 'deletedObjects')") }
            if ($doAction) {
                Write-PSFMessage -Tag "FolderRemove" -Level Verbose -Message "Remove folder '$($folderItem)'"
                $invokeParam = @{
                    "Field"        = "mailFolders/$($folderItem.Id)"
                    "User"         = $User
                    "Body"         = ""
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "Force"        = $true
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $null = Invoke-MgaRestMethodDelete @invokeParam
                if ($PassThru) {
                    $folderItem.InputObject
                }
            }
        }
    }

    end {
    }
}

function Rename-MgaMailFolder {
    <#
    .SYNOPSIS
        Rename folder(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Change the displayname of folder(s) in Exchange Online using the graph api.
 
    .PARAMETER Folder
        The folder to be renamed. This can be a name of the folder, it can be the
        Id of the folder or it can be a folder object passed in.
 
    .PARAMETER NewName
        The name to be set as new name.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> Rename-MgaMailFolder -Folder 'Inbox' -NewName 'MyPersonalInbox'
 
        Rename the "wellknown" folder inbox (regardless of it's current name), to 'MyPersonalInbox'.
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.Exchange.Mail.Folder])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByInputObject')]
        [Alias('FolderName', 'FolderId', 'InputObject', 'DisplayName', 'Name', 'Id')]
        [MSGraph.Exchange.Mail.FolderParameter[]]
        $Folder,

        [Parameter(Mandatory = $true, Position = 1)]
        [string]
        $NewName,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        $bodyJSON = @{
            displayName = $NewName
        } | ConvertTo-Json
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($folderItem in $Folder) {
            #region checking input object type and query folder if required
            if ($folderItem.TypeName -like "System.String") {
                $folderItem = Resolve-MailObjectFromString -Object $folderItem -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                if (-not $folderItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $folderItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($pscmdlet.ShouldProcess("Folder '$($folderItem)'", "Rename to '$($NewName)'")) {
                Write-PSFMessage -Tag "FolderUpdate" -Level Verbose -Message "Rename folder '$($folderItem)' to name '$($NewName)'"
                $invokeParam = @{
                    "Field"        = "mailFolders/$($folderItem.Id)"
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $output = Invoke-MgaRestMethodPatch @invokeParam
                if ($PassThru) {
                    New-MgaMailFolderObject -RestData $output -ParentFolder $folderItem.InputObject.ParentFolder -FunctionName $MyInvocation.MyCommand
                }
            }
        }
    }

    end {
    }
}

function Add-MgaMailMessageForward {
    <#
    .SYNOPSIS
        Forward message(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Creates forward message(s) and save it as draft message(s).
 
        Alternatively, the command can directly forward a message by specifing recipient(s) and text
        The message is then saved in the Sent Items folder.
 
    .PARAMETER Message
        Carrier object for Pipeline input.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER Comment
        The body of the message.
 
    .PARAMETER ToRecipients
        The To recipients for the message.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/message-createforward?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/message-forward?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> $mail | Add-MgaMailMessageForward
 
        Create forward message(s) and save it in the drafts folder for messages from variable $mail.
        also possible:
        PS C:\> Add-MgaMailMessageForward -Message $mail
 
        The variable $mail can be represent:
        PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail"
 
    .EXAMPLE
        PS C:\> $mail | Add-MgaMailMessageForward -ToRecipients 'someone@something.org' -Comment 'For your information.'
 
        This one directly forwards message(s) from variable $mail. The message(s) is saved in the sendItems folder
 
        The variable $mail can be represent:
        PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail"
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [Alias('Add-MgaMailForwardMessage')]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] #, ParameterSetName = 'ByInputObject'
        [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Mandatory = $true, ParameterSetName = 'DirectReply')]
        [Alias('To', 'Recipients')]
        [string[]]
        $ToRecipients,

        [Parameter(Mandatory = $true, ParameterSetName = 'DirectReply')]
        [Alias('Body', 'Text', 'ReplyText')]
        [String]
        $Comment,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [Parameter(ParameterSetName = 'DirectReply')]
        [switch]
        $PassThru
    )
    begin {
        if ($PSCmdlet.ParameterSetName -like 'DirectReply') {
            $requiredPermission = "Mail.Send"
        } else {
            $requiredPermission = "Mail.ReadWrite"
        }
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Debug -Message "Working on parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand

            if ($PSCmdlet.ParameterSetName -like 'DirectReply') {
                $bodyJSON = New-JsonMailObject -ToRecipients $ToRecipients -Comment $Comment -FunctionName $MyInvocation.MyCommand
                $msgAction = "Send"
            } else {
                $bodyJSON = ""
                $msgAction = "create"
            }
            #endregion checking input object type and query message if required

            #region send message
            $msg = $msgAction + " reply$(if($ReplyAll){" all"})"
            if ($pscmdlet.ShouldProcess($messageItem, $msg)) {
                Write-PSFMessage -Tag "MessageReply$msgAction" -Level Verbose -Message "$($msg) message for '$($messageItem)'"
                $invokeParam = @{
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                switch ($PSCmdlet.ParameterSetName) {
                    'Default' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/createForward") }
                    'DirectReply' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/forward") }
                    Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
                }

                $output = Invoke-MgaRestMethodPost @invokeParam
                if ($PSCmdlet.ParameterSetName -like 'Default' -and $output) {
                    New-MgaMailMessageObject -RestData $output -FunctionName $MyInvocation.MyCommand
                } elseif ($PSCmdlet.ParameterSetName -like 'DirectReply' -and $PassThru) {
                    Write-PSFMessage -Tag "MessageQuery" -Level Verbose -Message "PassThru specified, query forward message from sentItems folder."
                    Get-MgaMailMessage -FolderName Sentitems -Subject "FW: $($messageItem.Name)" -ResultSize 5
                }
            }
            #endregion send message
        }
    }

    end {
    }
}

function Add-MgaMailMessageReply {
    <#
    .SYNOPSIS
        Create reply (all) message(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Create reply (all) message(s) and save it as draft message(s).
 
        Alternatively, the command can directly send the reply (all) by specifing a text
        The message is then saved in the Sent Items folder.
 
    .PARAMETER Message
        Carrier object for Pipeline input.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER Comment
        The body of the message.
 
    .PARAMETER ReplyAll
        Creates a reply all message.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/message-createreply?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/message-reply?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/message-createreplyall?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/message-replyall?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> $mail | Add-MgaMailMessageReply
 
        Create reply message(s) for messages in variable $mail.
        also possible:
        PS C:\> Add-MgaMailMessageReply -Message $mail
 
        The variable $mail can be represent:
        PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail"
 
    .EXAMPLE
        PS C:\> $mail | Add-MgaMailMessageReply -Comment 'Reply for confirmation to your message.'
 
        This one directly send reply message(s) for messages from variable $mail.
        The message(s) is saved in the sendItems folder
 
        The variable $mail can be represent:
        PS C:\> $mail = Get-MgaMailMessage -Subject "Important mail"
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [Alias('Add-MgaMailReplyMessage')]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] #, ParameterSetName = 'ByInputObject'
        [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Mandatory = $true, ParameterSetName = 'DirectReply')]
        [Alias('Body', 'Text', 'ReplyText')]
        [String]
        $Comment,

        [Alias('All')]
        [switch]
        $ReplyAll,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [Parameter(ParameterSetName = 'DirectReply')]
        [switch]
        $PassThru
    )
    begin {
        if ($PSCmdlet.ParameterSetName -like 'DirectReply') {
            $requiredPermission = "Mail.Send"
        } else {
            $requiredPermission = "Mail.ReadWrite"
        }
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Debug -Message "Working on parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand

            if ($PSCmdlet.ParameterSetName -like 'DirectReply') {
                $bodyJSON = @{
                    comment = $Comment
                } | ConvertTo-Json
                $msgAction = "Send"
            } else {
                $bodyJSON = ""
                $msgAction = "create"
            }
            #endregion checking input object type and query message if required

            #region send message
            $msg = $msgAction + " reply$(if($ReplyAll){" all"})"
            if ($pscmdlet.ShouldProcess($messageItem, $msg)) {
                Write-PSFMessage -Tag "MessageReply$msgAction" -Level Verbose -Message "$($msg) message for '$($messageItem)'"
                $invokeParam = @{
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                switch ($PSCmdlet.ParameterSetName) {
                    'Default' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/createReply$(if($ReplyAll){"All"})") }
                    'DirectReply' { $invokeParam.Add("Field", "messages/$($messageItem.Id)/reply$(if($ReplyAll){"All"})") }
                    Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
                }

                $output = Invoke-MgaRestMethodPost @invokeParam
                if ($PSCmdlet.ParameterSetName -like 'Default' -and $output) {
                    New-MgaMailMessageObject -RestData $output -FunctionName $MyInvocation.MyCommand
                } elseif ($PSCmdlet.ParameterSetName -like 'DirectReply' -and $PassThru) {
                    Write-PSFMessage -Tag "MessageQuery" -Level Verbose -Message "PassThru specified, query reply message from sentItems folder."
                    Get-MgaMailMessage -FolderName Sentitems -Subject "RE: $($messageItem.Name)" -ResultSize 5
                }
            }
            #endregion send message
        }
    }

    end {
    }
}

function Copy-MgaMailMessage {
    <#
    .SYNOPSIS
        Copy message(s) to a folder
 
    .DESCRIPTION
        Copy message(s) to a folder in Exchange Online using the graph api.
 
    .PARAMETER Message
        Carrier object for Pipeline input. Accepts messages and strings.
 
    .PARAMETER DestinationFolder
        The destination folder where to copy the message to
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> $mails | Copy-MgaMailMessage -DestinationFolder $destinationFolder
 
        Copys messages in variable $mails to the folder in the variable $destinationFolder.
        also possible:
        PS C:\> Copy-MgaMailMessage -Message $mails -DestinationFolder $destinationFolder
 
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1
 
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    .EXAMPLE
        PS C:\> Copy-MgaMailMessage -Id $mails.id -DestinationFolder $destinationFolder
 
        Copys messages into the folder $destinationFolder.
 
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1
 
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage -Folder Inbox | Copy-MgaMailMessage -DestinationFolder $destinationFolder
 
        Copys ALL messages from your inbox into the folder $destinationFolder.
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [Alias()]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Mandatory = $true, Position = 1)]
        [MSGraph.Exchange.Mail.FolderParameter]
        $DestinationFolder,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region checking DestinationFolder and query folder if required
        if ($DestinationFolder.TypeName -like "System.String") {
            $DestinationFolder = Resolve-MailObjectFromString -Object $DestinationFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
            if (-not $DestinationFolder) { throw }
        }
        #endregion checking DestinationFolder and query folder if required

        $bodyHash = @{
            destinationId = ($DestinationFolder.Id | ConvertTo-Json)
        }
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings messages by parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        # Put parameters (JSON Parts) into a valid JSON-object together
        $bodyJSON = Merge-HashToJSON $bodyHash

        #region copy messages
        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($pscmdlet.ShouldProcess("message '$($messageItem)'", "Copy to folder '$($DestinationFolder.Name)'")) {
                Write-PSFMessage -Tag "MessageUpdate" -Level Verbose -Message "Copy message '$($messageItem)' to folder '$($DestinationFolder)'"
                $invokeParam = @{
                    "Field"        = "messages/$($messageItem.Id)/copy"
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $output = Invoke-MgaRestMethodPost @invokeParam
                if ($PassThru) { New-MgaMailMessageObject -RestData $output }
            }
        }
        #endregion Update messages
    }

}

function Get-MgaMailMessage {
    <#
    .SYNOPSIS
        Retrieves messages from a email folder from Exchange Online using the graph api.
 
    .DESCRIPTION
        Retrieves messages from a email folder from Exchange Online using the graph api.
 
    .PARAMETER InputObject
        Carrier object for Pipeline input
        Accepts messages or folders from other Mga-functions
 
    .PARAMETER FolderName
        The display name of the folder to search.
        Defaults to the inbox.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Subject
        The subject to filter by (Client Side filtering)
 
    .PARAMETER Delta
        Indicates a "delta-query" for incremental changes on mails.
        The switch allows you to query mutliple times against the same user and folder while only getting additional,
        updated or deleted messages.
 
        Please notice, that delta queries needs to be handeled right. See the examples for correct usage.
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage
 
        Return emails in the inbox of the user connected to through a token.
 
    .EXAMPLE
        PS C:\> $mails = Get-MgaMailMessage -Delta
 
        Return emails in the inbox of the user connected to through a token and write the output in the variable $mails.
        IMPORTANT, the -Delta switch needs to be specified on the first call, because the outputobject will has to be piped
        into the next delta query.
 
        The content of $mails can be used and processed:
        PS C:\> $mails
 
        So the second Get-MgaMailMessage call has to be:
        PS C:\> $deltaMails = Get-MgaMailMessage -InputObject $mails -Delta
 
        This return only unqueried, updated, or new messages from the previous call and writes the result in the
        variable $deltaMails.
 
        The content of the $deltaMails variable can be used as output and should only overwrites the $mail variable if there is content in $deltaMails:
        PS C:\> if($deltaMails) {
            $mails = $deltaMails
            $deltaMails
        }
 
        From the second call, the procedure can be continued as needed, only updates will be outputted by Get-MgaMailMessage.
 
        .EXAMPLE
        PS C:\> Get-MgaMailFolder -Name "Junkemail" | Get-MgaMailMessage
 
        Return emails from the Junkemail folder of the user connected to through a token.
 
        .EXAMPLE
        PS C:\> Get-MgaMailMessage -FolderName "MyFolder" -Subject "Important*"
 
        Return emails where the subject starts with "Important" from the folder "MyFolder" of the user connected to through a token.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(DefaultParameterSetName = 'ByInputObject')]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(ParameterSetName = 'ByInputObject', Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Input', 'Id')]
        [MSGraph.Exchange.Mail.MessageOrFolderParameter[]]
        $InputObject,

        [Parameter(ParameterSetName = 'ByFolderName', Position = 0)]
        [Alias('FolderId', 'Folder')]
        [string[]]
        $FolderName,

        [string]
        $User,

        [string]
        $Subject = "*",

        [switch]
        $Delta,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Mail.Read"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        $InvokeParams = @()
    }

    process {
        Write-PSFMessage -Level VeryVerbose -Message "Gettings mails by parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        if ($PSCmdlet.ParameterSetName -like "ByInputObject" -and -not $InputObject) {
            Write-PSFMessage -Level Verbose -Message "No InputObject specified. Gettings mail from default folder (inbox)." -Tag "ParameterSetHandling"
            [MSGraph.Exchange.Mail.MessageOrFolderParameter]$InputObject = [MSGraph.Exchange.Mail.WellKnownFolder]::Inbox.ToString()
        }
        if ($PSCmdlet.ParameterSetName -like "ByFolderName") {
            foreach ($folderItem in $FolderName) {
                $folderItem = [MSGraph.Exchange.Mail.MessageOrFolderParameter]$folderItem
                if ($folderItem.Name -and (-not $folderItem.IsWellKnownName)) {
                    [MSGraph.Exchange.Mail.MessageOrFolderParameter]$folderItem = Get-MgaMailFolder -Name $folderItem.Name -User $User -Token $Token
                }
                $InputObject = $InputObject + $folderItem
            }
        }

        foreach ($InputObjectItem in $InputObject) {
            Write-PSFMessage -Level VeryVerbose -Message "Parsing input $($InputObjectItem.TypeName) object '$($InputObjectItem)'"
            switch ($InputObjectItem.TypeName) {
                "MSGraph.Exchange.Mail.Message" {
                    if ($Delta -and ('@odata.deltaLink' -in $InputObjectItem.InputObject.BaseObject.psobject.Properties.Name)) {
                        # if delta message, construct a delta query from mail
                        Write-PSFMessage -Level VeryVerbose -Message "Delta parameter specified and delta message found. Checking on message '$($InputObjectItem)' from the pipeline"
                        $invokeParam = @{
                            "deltaLink"    = $InputObjectItem.InputObject.BaseObject.'@odata.deltaLink'
                            "Token"        = $Token
                            "ResultSize"   = $ResultSize
                            "FunctionName" = $MyInvocation.MyCommand
                        }
                    } else {
                        # if non delta message is parsed in, the message will be queried again (refreshed)
                        # Not really necessary, but works as intend from pipeline usage
                        Write-PSFMessage -Level VeryVerbose -Message "Refresh message '$($InputObjectItem)' from the pipeline"
                        $invokeParam = @{
                            "Field"        = "messages/$($InputObjectItem.Id)"
                            "User"         = $InputObjectItem.InputObject.BaseObject.User
                            "Token"        = $Token
                            "ResultSize"   = $ResultSize
                            "FunctionName" = $MyInvocation.MyCommand
                        }
                        if ($Delta) { $invokeParam.Add("Delta", $true) }
                    }
                    $invokeParams = $invokeParams + $invokeParam
                }

                "MSGraph.Exchange.Mail.Folder" {
                    Write-PSFMessage -Level VeryVerbose -Message "Gettings messages in folder '$($InputObjectItem)' from the pipeline"
                    $invokeParam = @{
                        "Field"        = "mailFolders/$($InputObjectItem.Id)/messages"
                        "User"         = $InputObjectItem.InputObject.User
                        "Token"        = $Token
                        "ResultSize"   = $ResultSize
                        "FunctionName" = $MyInvocation.MyCommand
                    }
                    if ($Delta) { $invokeParam.Add("Delta", $true) }
                    $invokeParams = $invokeParams + $invokeParam
                }

                "System.String" {
                    $invokeParam = @{
                        "User"         = $User
                        "Token"        = $Token
                        "ResultSize"   = $ResultSize
                        "FunctionName" = $MyInvocation.MyCommand
                    }
                    if ($Delta) { $invokeParam.Add("Delta", $true) }

                    $name = if ($InputObjectItem.IsWellKnownName) { $InputObjectItem.Name } else { $InputObjectItem.Id }
                    if ($name.length -eq 152 -or $name.length -eq 136) {
                        # Id is a message
                        Write-PSFMessage -Level VeryVerbose -Message "Gettings messages with Id '$($InputObjectItem)'" -Tag "InputValidation"
                        $invokeParam.Add("Field", "messages/$($name)")
                    } elseif ($name.length -eq 120 -or $name.length -eq 104) {
                        # Id is a folder
                        Write-PSFMessage -Level VeryVerbose -Message "Gettings messages in folder with Id '$($InputObjectItem)'" -Tag "InputValidation"
                        $invokeParam.Add("Field", "mailFolders/$($name)/messages")
                    } elseif ($InputObjectItem.IsWellKnownName -and $name) {
                        # a well known folder is specified by name
                        $invokeParam.Add("Field", "mailFolders/$($name)/messages")
                    } else {
                        # not a valid Id -> should not happen
                        Write-PSFMessage -Level Warning -Message "The specified Id seeams not be a valid Id. Skipping object '$($name)'" -Tag "InputValidation"
                        continue
                    }

                    $invokeParams = $invokeParams + $invokeParam
                    Remove-Variable -Name name -Force -ErrorAction Ignore -WhatIf:$false -Confirm:$false -Verbose:$false -Debug:$false
                }

                Default { Write-PSFMessage -Level Critical -Message "Failed on type validation. Can not handle $($InputObjectItem.TypeName)" -EnableException $true -Tag "TypeValidation" }
            }
        }
    }

    end {
        $fielList = @()
        $InvokeParamsUniqueList = @()
        Write-PSFMessage -Level Verbose -Message "Checking $( ($InvokeParams | Measure-Object).Count ) objects on unique calls..."
        foreach ($invokeParam in $InvokeParams) {
            if ($invokeParam.Field -and ($invokeParam.Field -notin $fielList)) {
                $InvokeParamsUniqueList = $InvokeParamsUniqueList + $invokeParam
                $fielList = $fielList + $invokeParam.Field
            } elseif ($invokeParam.deltaLink -notin $fielList) {
                $InvokeParamsUniqueList = $InvokeParamsUniqueList + $invokeParam
                $fielList = $fielList + $invokeParam.deltaLink
            }
        }
        Write-PSFMessage -Level Verbose -Message "Invoking $( ($InvokeParamsUniqueList | Measure-Object).Count ) REST calls for gettings messages"

        # run the message query and process the output
        foreach ($invokeParam in $InvokeParamsUniqueList) {
            $data = Invoke-MgaRestMethodGet @invokeParam | Where-Object { $_.subject -like $Subject }
            $output = foreach ($messageOutput in $data) {
                New-MgaMailMessageObject -RestData $messageOutput
            }
        }

        if ($output) {
            $output
        } else {
            Write-PSFMessage -Level Warning -Message "Message not found." -Tag "QueryData"
        }
    }
}

function Move-MgaMailMessage {
    <#
    .SYNOPSIS
        Move message(s) to a folder
 
    .DESCRIPTION
        Move message(s) to a folder in Exchange Online using the graph api.
 
    .PARAMETER Message
        Carrier object for Pipeline input. Accepts messages and strings.
 
    .PARAMETER DestinationFolder
        The destination folder where to move the message to
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> $mails | Move-MgaMailMessage -DestinationFolder $destinationFolder
 
        Moves messages in variable $mails to the folder in the variable $destinationFolder.
        also possible:
        PS C:\> Move-MgaMailMessage -Message $mails -DestinationFolder $destinationFolder
 
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1
 
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    .EXAMPLE
        PS C:\> Move-MgaMailMessage -Id $mails.id -DestinationFolder $destinationFolder
 
        Moves messages into the folder $destinationFolder.
 
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1
 
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    .EXAMPLE
        PS C:\> Get-MgaMailMessage -Folder Inbox | Move-MgaMailMessage -DestinationFolder $destinationFolder
 
        Moves ALL messages from your inbox into the folder $destinationFolder.
        The variable $destinationFolder can be represent:
        PS C:\> $destinationFolder = Get-MgaMailFolder -Name "Archive"
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [Alias()]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(Mandatory = $true, Position = 1)]
        [MSGraph.Exchange.Mail.FolderParameter]
        $DestinationFolder,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region checking DestinationFolder and query folder if required
        if ($DestinationFolder.TypeName -like "System.String") {
            $DestinationFolder = Resolve-MailObjectFromString -Object $DestinationFolder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
            if (-not $DestinationFolder) { throw }
        }
        #endregion checking DestinationFolder and query folder if required

        $bodyHash = @{
            destinationId = ($DestinationFolder.Id | ConvertTo-Json)
        }
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings messages by parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        # Put parameters (JSON Parts) into a valid JSON-object together and output the result
        $bodyJSON = Merge-HashToJson $bodyHash

        #region move message
        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($pscmdlet.ShouldProcess("message '$($messageItem)'", "Move to folder '$($DestinationFolder.Name)'")) {
                Write-PSFMessage -Tag "MessageUpdate" -Level Verbose -Message "Move message '$($messageItem)' to folder '$($DestinationFolder)'"
                $invokeParam = @{
                    "Field"        = "messages/$($messageItem.Id)/move"
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $output = Invoke-MgaRestMethodPost @invokeParam
                if ($PassThru) { New-MgaMailMessageObject -RestData $output }
            }
        }
        #endregion move message
    }

    end {
    }
}

function New-MgaMailMessage {
    <#
    .SYNOPSIS
        Creates a folder in Exchange Online using the graph api.
 
    .DESCRIPTION
        Creates a new folder in Exchange Online using the graph api.
 
    .PARAMETER Folder
        The folder where the new folder should be created in. Do not specify to create
        a folder on the root level.
 
        Possible values are a valid folder Id or a Mga folder object passed in.
        Tab completion is available on this parameter for a list of well known folders.
 
    .PARAMETER Subject
        The subject of the new message.
 
    .PARAMETER Sender
        The account that is actually used to generate the message.
        (Updatable only when sending a message from a shared mailbox or sending a message as a delegate.
        In any case, the value must correspond to the actual mailbox used.)
 
    .PARAMETER From
        The mailbox owner and sender of the message.
        Must correspond to the actual mailbox used.
 
    .PARAMETER ToRecipients
        The To recipients for the message.
 
    .PARAMETER CCRecipients
        The Cc recipients for the message.
 
    .PARAMETER BCCRecipients
        The Bcc recipients for the message.
 
    .PARAMETER ReplyTo
        The email addresses to use when replying.
 
    .PARAMETER Body
        The body of the message.
 
    .PARAMETER Categories
        The categories associated with the message.
 
    .PARAMETER Importance
        The importance of the message.
        The possible values are: Low, Normal, High.
 
    .PARAMETER InferenceClassification
        The classification of the message for the user, based on inferred relevance or importance, or on an explicit override.
        The possible values are: focused or other.
 
    .PARAMETER InternetMessageId
        The message ID in the format specified by RFC2822.
 
    .PARAMETER IsDeliveryReceiptRequested
        Indicates whether a delivery receipt is requested for the message.
 
    .PARAMETER IsReadReceiptRequested
        Indicates whether a read receipt is requested for the message.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information go to:
        https://docs.microsoft.com/en-us/graph/api/user-post-messages?view=graph-rest-1.0
 
    .LINK
 
    .EXAMPLE
        PS C:\> New-MgaMailMessage -ToRecipients 'someone@something.org' -Subject 'A new Mail' -Body 'This is a new mail'
 
        Creates a new message in the drafts folder
 
    .EXAMPLE
        PS C:\> New-MgaMailMessage -Subject 'A new Mail' -Folder 'MyFolder'
 
        Creates a new message in the folder named "MyFolder"
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [Alias('Name', 'Title')]
        [string[]]
        $Subject,

        [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'WithFolder')]
        [Alias('FolderName', 'FolderId')]
        [MSGraph.Exchange.Mail.FolderParameter]
        $Folder,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $Sender,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $From,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [Alias('To', 'Recipients')]
        [string[]]
        $ToRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $CCRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $BCCRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $ReplyTo,

        [String]
        $Body,

        [String[]]
        $Categories,

        [ValidateSet("Low", "Normal", "High")]
        [String]
        $Importance,

        [ValidateSet("focused", "other")]
        [String]
        $InferenceClassification,

        [String]
        $InternetMessageId,

        [bool]
        $IsDeliveryReceiptRequested,

        [bool]
        $IsReadReceiptRequested,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region checking input object type and query folder if required
        if ($Folder.TypeName -like "System.String") {
            $Folder = Resolve-MailObjectFromString -Object $Folder -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
            if (-not $Folder) { throw }
        }

        if ($Folder) {
            $User = Resolve-UserInMailObject -Object $Folder -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
        }
        #endregion checking input object type and query message if required
    }

    process {
        Write-PSFMessage -Level Debug -Message "Creating message(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        #region Put parameters (JSON Parts) into a valid "message"-JSON-object together
        $jsonParams = @{}

        $names = "Subject", "Sender", "From", "ToRecipients", "CCRecipients", "BCCRecipients", "ReplyTo", "Body", "Categories", "Importance", "InferenceClassification", "IsDeliveryReceiptRequested", "IsReadReceiptRequested"
        foreach ($name in $names) {
            if (Test-PSFParameterBinding -ParameterName $name) {
                Write-PSFMessage -Level Debug -Message "Add $($name) from parameters to message" -Tag "ParameterParsing"
                $jsonParams.Add($name, (Get-Variable $name -Scope 0).Value)
            }
        }

        $bodyJSON = New-JsonMailObject @jsonParams -FunctionName $MyInvocation.MyCommand
        #endregion Put parameters (JSON Parts) into a valid "message"-JSON-object together

        #region create messages
        if ($pscmdlet.ShouldProcess($Subject, "New")) {
            $msg = "Creating message '$($Subject)'"
            if ($Folder) { $msg = $msg + " in '$($Folder)'" } else { $msg = $msg + " in drafts folder" }
            Write-PSFMessage -Level Verbose -Message $msg -Tag "MessageCreation"

            $invokeParam = @{
                "User"         = $User
                "Body"         = $bodyJSON
                "ContentType"  = "application/json"
                "Token"        = $Token
                "FunctionName" = $MyInvocation.MyCommand
            }
            if ($Folder.Id) {
                $invokeParam.Add("Field", "mailFolders/$($Folder.Id)/messages")
            } else {
                $invokeParam.Add("Field", "messages")
            }

            $output = Invoke-MgaRestMethodPost @invokeParam
            if ($output) {
                New-MgaMailMessageObject -RestData $output -FunctionName $MyInvocation.MyCommand
            }
        }
        #endregion create messages
    }

    end {
    }
}

function Remove-MgaMailMessage {
    <#
    .SYNOPSIS
        Remove message(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Remove message(s) in Exchange Online using the graph api.
 
        ATTENTION! The command does what it is name to!
        The message will not be moved to 'deletedObjects', it will be deleted.
 
    .PARAMETER Message
        The message to be removed.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER Force
        If specified the user will not prompted on confirmation.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> Remove-MgaMailMessage -Message $message
 
        Removes message represented by the variable $message.
        This example will purge (the first 100 messages in) the inbox.
        You will be prompted for confirmation.
 
        The variable $message can be represent:
        PS C:\> $message = Get-MgaMailMessage -Folder Inbox
 
    .EXAMPLE
        PS C:\> $message | Remove-MgaMailMessage -Force
 
        Removes message represented by the variable $message.
        This example will purge (the first 100 messages in) the inbox.
        ATTENTION, there will be NO prompt for confirmation!
 
        The variable $mails can be represent:
        PS C:\> $message = Get-MgaMailMessage -Folder Inbox
 
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [switch]
        $Force,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings message(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($Force) { $doAction = $true } else { $doAction = $pscmdlet.ShouldProcess($messageItem, "Remove (ATTENTION! Message will not be moved to 'deletedObjects')") }
            if ($doAction) {
                Write-PSFMessage -Tag "MessageRemove" -Level Verbose -Message "Remove message '$($messageItem)'"
                $invokeParam = @{
                    "Field"        = "messages/$($messageItem.Id)"
                    "User"         = $User
                    "Body"         = ""
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "Force"        = $true
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $null = Invoke-MgaRestMethodDelete @invokeParam
                if ($PassThru) {
                    $messageItem.InputObject
                }
            }
        }
    }

    end {
    }
}

function Send-MgaMailMessage {
    <#
    .SYNOPSIS
        Send message(s) in Exchange Online using the graph api.
 
    .DESCRIPTION
        Send a previously created draft message(s) and save the message in the SendItems folder.
        Alternatively, the command can directly send a message by specifing a recipient, subject, ...
 
    .PARAMETER Message
        Carrier object for Pipeline input.
        This can be the id of the message or a message object passed in.
 
    .PARAMETER Subject
        The subject of the new message.
 
    .PARAMETER Sender
        The account that is actually used to generate the message.
        (Updatable only when sending a message from a shared mailbox or sending a message as a delegate.
        In any case, the value must correspond to the actual mailbox used.)
 
    .PARAMETER From
        The mailbox owner and sender of the message.
        Must correspond to the actual mailbox used.
 
    .PARAMETER ToRecipients
        The To recipients for the message.
 
    .PARAMETER CCRecipients
        The Cc recipients for the message.
 
    .PARAMETER BCCRecipients
        The Bcc recipients for the message.
 
    .PARAMETER ReplyTo
        The email addresses to use when replying.
 
    .PARAMETER Body
        The body of the message.
 
    .PARAMETER Categories
        The categories associated with the message.
 
    .PARAMETER Importance
        The importance of the message.
        The possible values are: Low, Normal, High.
 
    .PARAMETER InferenceClassification
        The classification of the message for the user, based on inferred relevance or importance, or on an explicit override.
        The possible values are: focused or other.
 
    .PARAMETER IsDeliveryReceiptRequested
        Indicates whether a delivery receipt is requested for the message.
 
    .PARAMETER IsReadReceiptRequested
        Indicates whether a read receipt is requested for the message.
 
    .PARAMETER SaveToSentItems
        Indicates whether to save the message in Sent Items.
        Only needed to be specified if the parameter should be $false, default is $true.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/message-send?view=graph-rest-1.0
        https://docs.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> $mail | Send-MgaMailMessage
 
        Send message(s) in variable $mail.
        also possible:
        PS C:\> Send-MgaMailMessage -Message $mail
 
        The variable $mail can be represent:
        PS C:\> $mail = New-MgaMailMessage -ToRecipients 'someone@something.org' -Subject 'A new Mail' -Body 'This is a new mail'
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'DirectSend')]
    [Alias()]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByInputObject')]
        [Alias('InputObject', 'MessageId', 'Id', 'Mail', 'MailId')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [Parameter(ParameterSetName = 'DirectSend', Mandatory = $true)]
        [Alias('Name', 'Title')]
        [string[]]
        $Subject,

        [Parameter(ParameterSetName = 'DirectSend')]
        [String]
        $Body,

        [Parameter(ParameterSetName = 'DirectSend', Mandatory = $true)]
        [Alias('To', 'Recipients')]
        [string[]]
        $ToRecipients,

        [Parameter(ParameterSetName = 'DirectSend')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $CCRecipients,

        [Parameter(ParameterSetName = 'DirectSend')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $Sender,

        [Parameter(ParameterSetName = 'DirectSend')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $From,

        [Parameter(ParameterSetName = 'DirectSend')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $BCCRecipients,

        [Parameter(ParameterSetName = 'DirectSend')]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $ReplyTo,

        [Parameter(ParameterSetName = 'DirectSend')]
        [String[]]
        $Categories,

        [Parameter(ParameterSetName = 'DirectSend')]
        [ValidateSet("Low", "Normal", "High")]
        [String]
        $Importance = "Normal",

        [Parameter(ParameterSetName = 'DirectSend')]
        [ValidateSet("focused", "other")]
        [String]
        $InferenceClassification = "other",

        [Parameter(ParameterSetName = 'DirectSend')]
        [bool]
        $IsDeliveryReceiptRequested = $false,

        [Parameter(ParameterSetName = 'DirectSend')]
        [bool]
        $IsReadReceiptRequested = $false,

        [Parameter(ParameterSetName = 'DirectSend')]
        [bool]
        $SaveToSentItems = $true,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.Send"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Debug -Message "Working on parameter set $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        #region send message
        switch ($PSCmdlet.ParameterSetName) {
            'ByInputObject' {
                foreach ($messageItem in $Message) {
                    #region checking input object type and query message if required
                    if ($messageItem.TypeName -like "System.String") {
                        $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                        if (-not $messageItem) { continue }
                    }

                    $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
                    #endregion checking input object type and query message if required

                    #region send message
                    if ($pscmdlet.ShouldProcess($messageItem, "Send")) {
                        Write-PSFMessage -Tag "MessageSend" -Level Verbose -Message "Send message '$($messageItem)'"
                        $invokeParam = @{
                            "Field"        = "messages/$($messageItem.Id)/send"
                            "User"         = $User
                            "Body"         = ""
                            "ContentType"  = "application/json"
                            "Token"        = $Token
                            "FunctionName" = $MyInvocation.MyCommand
                        }
                        $null = Invoke-MgaRestMethodPost @invokeParam
                        if ($PassThru) { $messageItem.InputObject }
                    }
                    #endregion send message
                }
            }

            'DirectSend' {
                #region Put parameters (JSON Parts) into a valid "message"-JSON-object together
                $jsonParams = @{}

                $names = "Subject", "Sender", "From", "ToRecipients", "CCRecipients", "BCCRecipients", "ReplyTo", "Body", "Categories", "Importance", "InferenceClassification", "IsDeliveryReceiptRequested", "IsReadReceiptRequested"
                foreach ($name in $names) {
                    if (Test-PSFParameterBinding -ParameterName $name) {
                        Write-PSFMessage -Level Debug -Message "Add $($name) from parameters to message" -Tag "ParameterParsing"
                        $jsonParams.Add($name, (Get-Variable $name -Scope 0).Value)
                    }
                }

                $bodyHash = @{
                    "message"         = (New-JsonMailObject @jsonParams -FunctionName $MyInvocation.MyCommand)
                    "saveToSentItems" = ($SaveToSentItems | ConvertTo-Json)
                }

                $bodyJSON = Merge-HashToJson $bodyHash
                #endregion Put parameters (JSON Parts) into a valid "message"-JSON-object together

                #region send message
                if ($pscmdlet.ShouldProcess($Subject, "Send")) {
                    Write-PSFMessage -Tag "MessageSend" -Level Verbose -Message "Send message with subject '$($Subject)' to recipient '$($ToRecipients)'"
                    $invokeParam = @{
                        "Field"        = "sendMail"
                        "User"         = $User
                        "Body"         = $bodyJSON
                        "ContentType"  = "application/json"
                        "Token"        = $Token
                        "FunctionName" = $MyInvocation.MyCommand
                    }
                    $null = Invoke-MgaRestMethodPost @invokeParam
                    if ($PassThru) { $messageItem.InputObject }
                }
                #endregion send message
            }

            Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
        }
        #endregion send message
    }

    end {
    }
}

function Set-MgaMailMessage {
    <#
    .SYNOPSIS
        Set properties on message(s)
 
    .DESCRIPTION
        Set properties on message(s) in Exchange Online using the graph api.
 
    .PARAMETER Message
        Carrier object for Pipeline input. Accepts messages.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER IsRead
        Indicates whether the message has been read.
 
    .PARAMETER Subject
        The subject of the message.
        (Updatable only if isDraft = true.)
 
    .PARAMETER Sender
        The account that is actually used to generate the message.
        (Updatable only if isDraft = true, and when sending a message from a shared mailbox,
        or sending a message as a delegate. In any case, the value must correspond to the actual mailbox used.)
 
    .PARAMETER From
        The mailbox owner and sender of the message.
        Must correspond to the actual mailbox used.
        (Updatable only if isDraft = true.)
 
    .PARAMETER ToRecipients
        The To recipients for the message.
        (Updatable only if isDraft = true.)
 
    .PARAMETER CCRecipients
        The Cc recipients for the message.
        (Updatable only if isDraft = true.)
 
    .PARAMETER BCCRecipients
        The Bcc recipients for the message.
        (Updatable only if isDraft = true.)
 
    .PARAMETER ReplyTo
        The email addresses to use when replying.
        (Updatable only if isDraft = true.)
 
    .PARAMETER Body
        The body of the message.
        (Updatable only if isDraft = true.)
 
    .PARAMETER Categories
        The categories associated with the message.
 
    .PARAMETER Importance
        The importance of the message.
        The possible values are: Low, Normal, High.
 
    .PARAMETER InferenceClassification
        The classification of the message for the user, based on inferred relevance or importance, or on an explicit override.
        The possible values are: focused or other.
 
    .PARAMETER InternetMessageId
        The message ID in the format specified by RFC2822.
        (Updatable only if isDraft = true.)
 
    .PARAMETER IsDeliveryReceiptRequested
        Indicates whether a delivery receipt is requested for the message.
 
    .PARAMETER IsReadReceiptRequested
        Indicates whether a read receipt is requested for the message.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the token to the console
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> $mail | Set-MgaMailMessage -IsRead $false
 
        Set messages represented by variable $mail to status "unread"
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1
 
    .EXAMPLE
        PS C:\> $mail | Set-MgaMailMessage -IsRead $false -categories "Red category"
 
        Set status "unread" and category "Red category" to messages represented by variable $mail
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Inbox -ResultSize 1
 
    .EXAMPLE
        PS C:\> $mail | Set-MgaMailMessage -ToRecipients "someone@something.org"
 
        Set reciepent from draft mail represented by variable $mail
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Drafts
 
    .EXAMPLE
        PS C:\> Set-MgaMailMessage -Id $mail.Id -ToRecipients "someone@something.org" -Subject "Something important"
 
        Set reciepent from draft mail represented by variable $mail
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Drafts
 
    .EXAMPLE
        PS C:\> $mail | Set-MgaMailMessage -ToRecipients $null
 
        Clear reciepent from draft mail represented by variable $mail
        The variable $mails can be represent:
        PS C:\> $mails = Get-MgaMailMessage -Folder Drafts
 
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidAssignmentToAutomaticVariable", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'Default')]
    [Alias("Update-MgaMailMessage")]
    [OutputType([MSGraph.Exchange.Mail.Message])]
    param (
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('InputObject', 'MessageId', 'Id')]
        [MSGraph.Exchange.Mail.MessageParameter[]]
        $Message,

        [ValidateNotNullOrEmpty()]
        [bool]
        $IsRead,

        [string]
        $Subject,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $Sender,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string]
        $From,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [Alias('To', 'Recipients')]
        [string[]]
        $ToRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $CCRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $BCCRecipients,

        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [string[]]
        $ReplyTo,

        [String]
        $Body,

        [String[]]
        $Categories,

        [ValidateSet("Low", "Normal", "High")]
        [String]
        $Importance,

        [ValidateSet("focused", "other")]
        [String]
        $InferenceClassification,

        [String]
        $InternetMessageId,

        [bool]
        $IsDeliveryReceiptRequested,

        [bool]
        $IsReadReceiptRequested,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "Mail.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Debug -Message "Gettings folder(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        #region Put parameters (JSON Parts) into a valid "message"-JSON-object together
        $jsonParams = @{}
        $boundParameters = @()
        $names = "IsRead", "Subject", "Sender", "From", "ToRecipients", "CCRecipients", "BCCRecipients", "ReplyTo", "Body", "Categories", "Importance", "InferenceClassification", "InternetMessageId", "IsDeliveryReceiptRequested", "IsReadReceiptRequested"
        foreach ($name in $names) {
            if (Test-PSFParameterBinding -ParameterName $name) {
                Write-PSFMessage -Level Debug -Message "Add $($name) from parameters to message" -Tag "ParameterParsing"
                $boundParameters = $boundParameters + $name
                $jsonParams.Add($name, (Get-Variable $name -Scope 0).Value)
            }
        }
        $bodyJSON = New-JsonMailObject @jsonParams -FunctionName $MyInvocation.MyCommand
        #endregion Put parameters (JSON Parts) into a valid "message"-JSON-object together

        #region Update messages
        foreach ($messageItem in $Message) {
            #region checking input object type and query message if required
            if ($messageItem.TypeName -like "System.String") {
                $messageItem = Resolve-MailObjectFromString -Object $messageItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $messageItem) { continue }
            }

            $User = Resolve-UserInMailObject -Object $messageItem -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
            #endregion checking input object type and query message if required

            if ($pscmdlet.ShouldProcess("message '$($messageItem)'", "Update properties '$([string]::Join("', '", $boundParameters))'")) {
                Write-PSFMessage -Tag "MessageUpdate" -Level Verbose -Message "Update properties '$([string]::Join("', '", $boundParameters))' on message '$($messageItem)'"
                $invokeParam = @{
                    "Field"        = "messages/$($messageItem.Id)"
                    "User"         = $User
                    "Body"         = $bodyJSON
                    "ContentType"  = "application/json"
                    "Token"        = $Token
                    "FunctionName" = $MyInvocation.MyCommand
                }
                $output = Invoke-MgaRestMethodPatch @invokeParam
                if ($output -and $PassThru) { New-MgaMailMessageObject -RestData $output }
            }
        }
        #endregion Update messages
    }

}

function Get-MgaMailboxSetting {
    <#
    .SYNOPSIS
        Get the mailbox settings from Exchange Online using the graph api.
 
    .DESCRIPTION
        Get the mailbox settings from Exchange Online using the graph api.
        This includes settings for automatic replies (notify people automatically
        upon receipt of their email), locale (language and country/region),
        and time zone, and working hours.
 
        You can view all mailbox settings, or get specific settings by
        specifing switch parameters.
 
    .PARAMETER AutomaticReplySetting
        If specified, only the settings for automatic notifications to
        senders of an incoming email are outputted.
 
        Fun fact:
        Here's an interesting historical question - when we say Out of Office,
        why does it sometimes get shortened to ‘OOF’? Shouldn’t it be ‘OOO’?
        https://blogs.technet.microsoft.com/exchange/2004/07/12/why-is-oof-an-oof-and-not-an-ooo/
 
    .PARAMETER LanguageSetting
        If specified, only the information about the locale, including the
        preferred language and country/region are displayed.
 
    .PARAMETER TimeZoneSetting
        If specified, only the timezone settings from the users mailbox are displayed.
 
    .PARAMETER WorkingHoursSetting
        If specified, only the settings for the days of the week and hours in a
        specific time zone that the user works are displayed.
 
    .PARAMETER ArchiveFolderSetting
        If specified, only the archive folder settings from the users mailbox are displayed.
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/user-get-mailboxsettings?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting
 
        Return all mailbox settings for the user connected to through the registered token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting -AutomaticReplySetting
 
        Return only the settings for automatic notifications to senders of an incoming email
        for the user connected to through the registered token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting -LanguageSetting
 
        Return only the information about the locale, including the preferred language and
        country/region, for the user connected to through the registered token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting -TimeZoneSetting
 
        Return only the timezone settings for the user connected to through the registered token.
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting -WorkingHoursSetting
 
        Return only the settings for the days of the week and hours in a specific time zone
        the user connected to through the registered token works.
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting -ArchiveFolderSetting
 
        Return only the settings for the folder where mails are archived in the user
        connected to through the registered token works.
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'AllSettings')]
    param (
        [Parameter(ParameterSetName = 'AutomaticReplySetting')]
        [Alias('AutoReply', 'OutOfOffice', 'OutOfOfficeSetting', 'OOFSettings', 'OOF')]
        [switch]
        $AutomaticReplySetting,

        [Parameter(ParameterSetName = 'LanguageSetting')]
        [Alias('Language')]
        [switch]
        $LanguageSetting,

        [Parameter(ParameterSetName = 'TimeZoneSetting')]
        [Alias('TimeZone')]
        [switch]
        $TimeZoneSetting,

        [Parameter(ParameterSetName = 'WorkingHoursSetting')]
        [Alias('WorkingsHour')]
        [switch]
        $WorkingHoursSetting,

        [Parameter(ParameterSetName = 'ArchiveFolderSetting')]
        [Alias('ArchiveFolder')]
        [switch]
        $ArchiveFolderSetting,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token
    )
    begin {
        $requiredPermission = "MailboxSettings.Read"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand
    }

    process {
        Write-PSFMessage -Level Verbose -Message "Getting mailbox settings for '$(Resolve-UserString -User $User)' by ParameterSet $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"

        #region query data
        $invokeParam = @{
            "Token"        = $Token
            "User"         = $User
            "FunctionName" = $MyInvocation.MyCommand
        }
        switch ($PSCmdlet.ParameterSetName) {
            {$_ -like 'AllSettings' -or $_ -like 'ArchiveFolderSetting'} { $invokeParam.Add('Field', 'mailboxSettings') }
            #'AllSettings' { $invokeParam.Add('Field', 'mailboxSettings') }
            'AutomaticReplySetting' { $invokeParam.Add('Field', 'mailboxSettings/automaticRepliesSetting') }
            'LanguageSetting' { $invokeParam.Add('Field', 'mailboxSettings/language') }
            'TimeZoneSetting' { $invokeParam.Add('Field', 'mailboxSettings/timeZone') }
            'WorkingHoursSetting' { $invokeParam.Add('Field', 'mailboxSettings/workingHours') }
            Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
        }

        $data = Invoke-MgaRestMethodGet @invokeParam
        #endregion query data

        #region output data
        foreach ($output in $data) {
            $mailboxSettingObject = New-MgaMailboxSettingObject -RestData $output -Type $PSCmdlet.ParameterSetName -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
            $mailboxSettingObject
        }
        #endregion output data
    }

    end {
    }
}

function Set-MgaMailboxSetting {
    <#
    .SYNOPSIS
        Set the mailbox settings from Exchange Online using the graph api.
 
    .DESCRIPTION
        set the mailbox settings in Exchange Online using the graph api.
        This includes settings for automatic replies (notify people automatically
        upon receipt of their email), locale (language and country/region),
        and time zone, and working hours.
 
        You can parse in modified settings from Get-MgaMailboxSetting command.
 
    .PARAMETER InputObject
        Carrier object for Pipeline input. Accepts all the different setting objects
        outputted by Get-MgaMailboxSetting.
 
    .PARAMETER AutomaticReply
        If specified, the command will set AutomaticReply settings
 
    .PARAMETER Language
        If specified, the command will set Language settings
 
    .PARAMETER TimeZone
        If specified, the command will set TimeZone settings
 
    .PARAMETER WorkingHours
        If specified, the command will set WorkingHour settings
 
    .PARAMETER User
        The user-account to access. Defaults to the main user connected as.
        Can be any primary email name of any user the connected token has access to.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .PARAMETER PassThru
        Outputs the mailbox settings to the console.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
 
    .NOTES
        For addiontional information about Microsoft Graph API go to:
        https://docs.microsoft.com/en-us/graph/api/user-update-mailboxsettings?view=graph-rest-1.0
 
    .EXAMPLE
        PS C:\> Set-MgaMailboxSetting
 
        Return all mailbox settings for the user connected to through the registered token.
 
    .EXAMPLE
        PS C:\> Set-MgaMailboxSetting -AutomaticReply
 
        Set
 
    .EXAMPLE
        PS C:\> Set-MgaMailboxSetting -Language
 
        Set
 
    .EXAMPLE
        PS C:\> Set-MgaMailboxSetting -TimeZone
 
        Set
 
    .EXAMPLE
        PS C:\> Get-MgaMailboxSetting -WorkingHours
 
        Set
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'InputObject')]
    param (
        [Parameter(ParameterSetName = 'InputObject')]
        [Alias('MailboxSetting', 'ArchiveFolderSetting', 'AutomaticReplySetting', 'LanguageSetting', 'TimeZoneSetting', 'WorkingHoursSetting', 'Setting', 'SettingObject')]
        [MSGraph.Exchange.MailboxSetting.MailboxSettingParameter]
        $InputObject,

        [Parameter(ParameterSetName = 'AutomaticReplySetting')]
        [Alias()]
        [switch]
        $AutomaticReply,

        [Parameter(ParameterSetName = 'LanguageSetting')]
        [Alias()]
        [switch]
        $Language,

        [Parameter(ParameterSetName = 'TimeZoneSetting')]
        [Alias()]
        [switch]
        $TimeZone,

        [Parameter(ParameterSetName = 'WorkingHoursSetting')]
        [Alias()]
        [switch]
        $WorkingHours,

        [string]
        $User,

        [MSGraph.Core.AzureAccessToken]
        $Token,

        [switch]
        $PassThru
    )
    begin {
        $requiredPermission = "MailboxSettings.ReadWrite"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        $invokeParam = @{
            "Field"        = "mailboxSettings"
            "Token"        = $Token
            "User"         = $User
            "FunctionName" = $MyInvocation.MyCommand
        }
    }

    process {
        #region prepare rest data
        switch ($PSCmdlet.ParameterSetName) {
            'InputObject' {
                Write-PSFMessage -Level Verbose -Message "Working on mailbox settings '$($InputObject)' for '$(Resolve-UserString -User $User)' by ParameterSet $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
                $User = Resolve-UserInMailObject -Object $InputObject -User $User -ShowWarning -FunctionName $MyInvocation.MyCommand
                $bodyJSON = New-JsonMailboxSettingObject -SettingObject $InputObject -User $User -FunctionName $MyInvocation.MyCommand
                $invokeParam.Add('Body', $bodyJSON)
            }

            Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
        }
        #endregion prepare rest data

        if ($pscmdlet.ShouldProcess("mailbox of '$(Resolve-UserString -User $User -ContextData)'", "Set $InputObject")) {
            # set data
            $data = Invoke-MgaRestMethodPatch @invokeParam

            #region output data
            if($PassThru) {
                foreach ($output in $data) {
                    $mailboxSettingObject = New-MgaMailboxSettingObject -RestData $output -Type $InputObject.Name -User $User -Token $Token -FunctionName $MyInvocation.MyCommand
                    $mailboxSettingObject
                }
            }
            #endregion output data
        }
    }

    end {
    }
}

function Get-MgaTeam {
    <#
    .SYNOPSIS
        Get Microsoft Teams Team
 
    .DESCRIPTION
        Get Microsoft Teams Team(s) with current settings via Microsoft Graph API
 
        The command can gather teams where the current connected user is joined,
        or list all existing teams in the tenant. Detailed settings for a team are
        only showed, if the connected user has appropriate permissions for the team.
 
    .PARAMETER Name
        The name of the team(s) to query.
        (Client Side filtering)
 
    .PARAMETER Id
        The Id of the team(s) to query.
        (Client Side filtering)
 
    .PARAMETER InputObject
        A team object piped in to refresh data.
 
    .PARAMETER ListAll
        Show all available teams in the whole tenant.
        As default behaviour, only teams where the current user is joined will be shown.
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaTeam
 
        Returns all teams the connected user is joined to.
 
    .EXAMPLE
        PS C:\> Get-MgaTeam -Name "MyTeam*"
 
        Returns all teams starting with name "MyTeam" where the connected user is joined to.
 
    .EXAMPLE
        PS C:\> Get-MgaTeam -InputObject $team
 
        Returns refreshed info for the team out of the variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> Get-MgaTeam -ListAll
 
        Returns all teams in the tenant.
        Detailed information about configuration for the team is only listed, when the connected user
        has appropriate permissions (administrative permissions or joined member of the team).
 
        If the user has permissions is indicated by the property "Accessible".
 
    .EXAMPLE
        PS C:\> Get-MgaTeam -Name "Sales*" -ListAll
 
        Returns all teams in the tenant starting with name "Sales".
        Detailed information about configuration for the team is only listed, when the connected user
        has appropriate permissions (administrative permissions or joined member of the team).
 
        If the user has permissions is indicated by the property "Accessible".
 
    .EXAMPLE
        PS C:\> Get-MgaTeam -ResultSize 5 -Token $Token
 
        Retrieves only the first 5 teams for the connected user with the token represented in the variable $token.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'ByName')]
    [OutputType([MSGraph.Teams.Team])]
    param (
        [Parameter(ParameterSetName = 'ByInputOBject', ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true, Position = 0)]
        [Alias('Team')]
        [MSGraph.Teams.TeamParameter[]]
        $InputObject,

        [Parameter(ParameterSetName = 'ByName', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false, Position = 0)]
        [Parameter(ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)]
        [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)]
        [Parameter(ParameterSetName = 'ById', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)]
        [Alias('IdFilter', 'FilterId')]
        [string]
        $Id,

        [Parameter(ParameterSetName = 'ListAll', ValueFromPipeline = $false, ValueFromPipelineByPropertyName = $false, Mandatory = $false)]
        [switch]
        $ListAll,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Group.Read.All"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        $requiredPermission = "Team.ReadBasic.All"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region helper subfunctions
        function invoke-internalMgaGetTeamsDetail ([psobject[]]$teamList, $token, $resultSize, [String]$functionName) {
            # Subfunction for query team information
            foreach ($teamListItem in $teamList) {
                Write-PSFMessage -Level VeryVerbose -Message "Getting details on team '$($teamListItem.displayName)'" -Tag "QueryData" -FunctionName $functionName
                $invokeParam = @{
                    "Field"          = "teams/$($teamListItem.id)"
                    "Token"          = $token
                    "UserUnspecific" = $true
                    "ResultSize"     = $resultSize
                    "FunctionName"   = $functionName
                }

                $teamInfo = [PSCustomObject]@{ }
                try {
                    $teamInfo = Invoke-MgaRestMethodGet @invokeParam
                } catch {
                    Write-PSFMessage -Level VeryVerbose -Message "Unable to query information on team '$($teamListItem.displayName)'. Assuming no permission or membership." -Tag "QueryData" -FunctionName $functionName
                    $teamInfo | Add-Member -MemberType NoteProperty -Name id -Value $teamListItem.id
                    $teamInfo | Add-Member -MemberType NoteProperty -Name isArchived -Value $teamListItem.isArchived
                    $teamInfo | Add-Member -MemberType NoteProperty -Name User -Value $teamListItem.User
                }
                $teamInfo | Add-Member -MemberType NoteProperty -Name displayName -Value "$($teamListItem.displayName)" -ErrorAction SilentlyContinue
                $teamInfo | Add-Member -MemberType NoteProperty -Name description -Value "$($teamListItem.description)" -ErrorAction SilentlyContinue

                $teamInfo
            }
        }
        #endregion helper subfunctions
    }

    process {
        Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        [array]$data = @()
        $invokeParam = @{
            "Token"        = $Token
            "ResultSize"   = $ResultSize
            "FunctionName" = $MyInvocation.MyCommand
        }
        switch ($PSCmdlet.ParameterSetName) {
            { $_ -in 'ByName', 'ById' } {
                Write-PSFMessage -Level Verbose -Message "Gettings joined team(s) for user $($token.UserprincipalName)" -Tag "QueryData"
                $invokeParam.Add('Field', 'me/joinedTeams')
                $invokeParam.Add('UserUnspecific', $true)
                [array]$teamList = Invoke-MgaRestMethodGet @invokeParam

                if ($PSCmdlet.ParameterSetName -like 'ByName' -and $Name) { [array]$teamList = $teamList | Where-Object displayName -Like $Name }
                if ($PSCmdlet.ParameterSetName -like 'ById' -and $Id) { [array]$teamList = $teamList | Where-Object Id -Like $Id }

                if ($teamList) {
                    Write-PSFMessage -Level VeryVerbose -Message "Found $($teamList.Count) team(s) for user $($token.UserprincipalName)" -Tag "QueryData"
                    $teamList = invoke-internalMgaGetTeamsDetail -teamList $teamList -token $Token -resultSize $ResultSize -functionName $MyInvocation.MyCommand

                    $teamList = $teamList | Add-Member -MemberType NoteProperty -Name "InfoFromJoinedTeam" -Value $true -PassThru
                    $data = $data + $teamList
                } else {
                    Stop-PSFFunction -Message "No joined teams found for user $($token.TokenOwner)" -Tag "QueryData"
                }
            }

            'ListAll' {
                Write-PSFMessage -Level Verbose -Message "Gettings all team(s) from the tenant" -Tag "QueryData"
                #Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData"
                $invokeParam.Add('Field', "groups?`$select=id,displayname,description,resourceProvisioningOptions")
                $invokeParam.Add('UserUnspecific', $true)
                [array]$teamList = Invoke-MgaRestMethodGet @invokeParam | Where-Object resourceProvisioningOptions -like "Team"

                if ($Name) { [array]$teamList = $teamList | Where-Object displayName -Like $Name }
                if ($Id) { [array]$teamList = $teamList | Where-Object Id -Like $Id }

                if ($teamList) {
                    Write-PSFMessage -Level VeryVerbose -Message "Found $($teamList.Count) team(s) in tenant" -Tag "QueryData"
                    $teamList = invoke-internalMgaGetTeamsDetail -teamList $teamList -token $Token -resultSize $ResultSize -functionName $MyInvocation.MyCommand

                    $teamList = $teamList | Add-Member -MemberType NoteProperty -Name "InfoFromJoinedTeam" -Value $false -PassThru
                    $data = $data + $teamList
                } else {
                    Stop-PSFFunction -Message "Unexpected Error while getting all teams team information from the tenant." -Tag "QueryData"
                }
            }

            'ByInputOBject' {
                foreach ($team in $InputOBject) {
                    Write-PSFMessage -Level Verbose -Message "Getting team '$($team)'" -Tag "ParameterSetHandling"
                    # resolve team via name
                    if ($team.TypeName -like "System.String") {
                        if ($team.Name) {
                            # get team by name
                            $teamQueried = Get-MgaTeam -Name $team.Name -ListAll -ResultSize 0 -Token $Token
                        } else {
                            # get team by Id
                            $teamQueried = Get-MgaTeam -Id $team.Id -ListAll -ResultSize 0 -Token $Token
                        }
                    } else {
                        # a previsouly query team is piped in
                        if ($team.InputObject.InfoFromJoinedTeam) {
                            $teamQueried = Get-MgaTeam -Name $team.Name -Token $Token
                        } else {
                            $teamQueried = Get-MgaTeam -Name $team.Name -ListAll -ResultSize 0 -Token $Token
                        }
                    }

                    if ($teamQueried) {
                        $teamQueried
                    } else {
                        Stop-PSFFunction -Message "Unexpected Error while getting information on team '$($team)'" -Tag "QueryData"
                    }
                }
            }

            Default { Stop-PSFFunction -Message "Unhandled parameter set. ($($PSCmdlet.ParameterSetName)) Developer mistake." -EnableException $true -Category MetadataError -FunctionName $MyInvocation.MyCommand }
        }

        #region output data
        Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData"
        foreach ($output in $data) {
            if ($output.memberSettings) {
                # team object with accessible information
                $teamObject = [MSGraph.Teams.Team]::new(
                    $output.id,
                    $output.internalId,
                    $output.displayName,
                    $output.description,
                    $output.user,
                    $output.isArchived,
                    $output.InfoFromJoinedTeam,
                    $output.webUrl,
                    $output
                )

                $memberSetting = [MSGraph.Teams.TeamMemberSettings]::new(
                    $output.memberSettings.allowCreateUpdateChannels,
                    $output.memberSettings.allowDeleteChannels,
                    $output.memberSettings.allowAddRemoveApps,
                    $output.memberSettings.allowCreateUpdateRemoveTabs,
                    $output.memberSettings.allowCreateUpdateRemoveConnectors
                )
                $teamObject.memberSettings = $memberSetting

                $guestSettings = [MSGraph.Teams.TeamGuestSettings]::new(
                    $output.guestSettings.allowCreateUpdateChannels,
                    $output.guestSettings.allowDeleteChannels
                )
                $teamObject.guestSettings = $guestSettings

                $messagingSettings = [MSGraph.Teams.TeamMessagingSettings]::new(
                    $output.messagingSettings.allowUserEditMessages,
                    $output.messagingSettings.allowUserDeleteMessages,
                    $output.messagingSettings.allowOwnerDeleteMessages,
                    $output.messagingSettings.allowTeamMentions,
                    $output.messagingSettings.allowChannelMentions
                )
                $teamObject.messagingSettings = $messagingSettings

                $funSettings = [MSGraph.Teams.TeamFunSettings]::new(
                    $output.funSettings.allowGiphy,
                    $output.funSettings.giphyContentRating,
                    $output.funSettings.allowStickersAndMemes,
                    $output.funSettings.allowCustomMemes
                )
                $teamObject.funSettings = $funSettings
            } else {
                # minimal team object. Basically, just information about the group
                $teamObject = [MSGraph.Teams.Team]::new(
                    $output.id,
                    $output.displayName,
                    $output.description,
                    $output.user,
                    $output.isArchived,
                    $output.InfoFromJoinedTeam
                )
            }

            Write-PSFMessage -Level Debug -Message "Output new object '$($teamObject)'." -Tag "OutputData"
            $teamObject
        }
        #endregion output data
    }

    end {
    }
}

function Get-MgaTeamChannel {
    <#
    .SYNOPSIS
        Get channels from a Microsoft Teams Team
 
    .DESCRIPTION
        Get channel(s) from Microsoft Teams Team(s) with current settings via Microsoft Graph API
 
    .PARAMETER InputObject
        A team object piped in.
 
    .PARAMETER Name
        The name of the team(s) to query.
        (Client Side filtering)
 
    .PARAMETER Id
        The Id of the team(s) to query.
        (Client Side filtering)
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaTeamChannel $team
 
        Returns all channels from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> $team | Get-MgaTeamChannel -Name "General"
 
        Returns the Channel "General" from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> $team | Get-MgaTeamChannel -ResultSize 5
 
        Retrieves only the first 5 channels from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
    #>

    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.Teams.TeamChannel])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('Team', 'TeamName', 'TeamID')]
        [MSGraph.Teams.TeamParameter[]]
        $InputObject,

        [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')]
        [string]
        $Name,

        [Alias('FilterId', 'IdFilter')]
        [string]
        $Id,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Group.Read.All"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region helper subfunctions
        #endregion helper subfunctions
    }

    process {
        Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) channel by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        #Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData"

        foreach ($teamItem in $InputObject) {
            #region checking input object type and query message if required
            if ($teamItem.psobject.TypeNames[0] -like "System.String") {
                $teamItem = Resolve-MailObjectFromString -Object $teamItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $teamItem) { continue }
            }
            #endregion checking input object type and query message if required


            #region query data
            $invokeParam = @{
                "Field"          = "teams/$($teamItem.Id)/channels"
                "Token"          = $Token
                'UserUnspecific' = $true
                "ResultSize"     = $ResultSize
                "ApiVersion"     = "v1.0"
                "FunctionName"   = $MyInvocation.MyCommand
            }
            Write-PSFMessage -Level Verbose -Message "Getting channel from team '$($teamItem)'" -Tag "QueryData"
            $data = Invoke-MgaRestMethodGet @invokeParam
            if ($Name) { [array]$data = $data | Where-Object displayName -Like $Name }
            if ($Id) { [array]$data = $data | Where-Object Id -Like $Id }

            #endregion query data


            #region output data
            Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData"
            foreach ($output in $data) {
                if($output.Email) { $_email = [mailaddress]::new($output.Email) } else { $_email = $null }

                $outputObject = [MSGraph.Teams.TeamChannel]::new(
                    $output.Id,
                    $output.DisplayName,
                    $output.Description,
                    $output.isFavoriteByDefault,
                    $output.WebUrl,
                    $_email,
                    $output.User,
                    $output
                )

                Write-PSFMessage -Level Debug -Message "Output channel '$($outputObject)'." -Tag "OutputData"
                $outputObject
            }
            #endregion output data
        }

    }

    end {
    }
}

function Get-MgaTeamMember {
    <#
    .SYNOPSIS
        Get members from a Microsoft Teams Team
 
    .DESCRIPTION
        Get members from Microsoft Teams Team(s) via Microsoft Graph API
 
    .PARAMETER InputObject
        A team object where to get members from.
 
    .PARAMETER Name
        Name filter for the members to query.
        (Client Side filtering)
 
    .PARAMETER Id
        Id filter for the members to query.
        (Client Side filtering)
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaTeamMember $team
 
        Returns all members from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> $team | Get-MgaTeamMember -Name "*John*"
 
        Returns any member contains "John" in his name from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> $team | Get-MgaTeamMember -ResultSize 5
 
        Retrieves only the first 5 members from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
    #>

    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.AzureAD.Users.User])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('Team', 'TeamName', 'TeamID')]
        [MSGraph.Teams.TeamParameter[]]
        $InputObject,

        [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')]
        [string]
        $Name,

        [Alias('FilterId', 'IdFilter')]
        [string]
        $Id,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Group.Read.All"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region helper subfunctions
        #endregion helper subfunctions
    }

    process {
        Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) member by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData"

        foreach ($teamItem in $InputObject) {
            #region checking input object type and query message if required
            if ($teamItem.TypeName -like "System.String") {
                $teamItem = Resolve-MailObjectFromString -Object $teamItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $teamItem) { continue }
            }
            #endregion checking input object type and query message if required


            #region query data
            $invokeParam = @{
                "Field"          = "groups/$($teamItem.Id)/members"
                "Token"          = $Token
                'UserUnspecific' = $true
                "ResultSize"     = $ResultSize
                "ApiVersion"     = "beta"
                "FunctionName"   = $MyInvocation.MyCommand
            }
            Write-PSFMessage -Level Verbose -Message "Getting team '$($teamItem)' members" -Tag "QueryData"
            $data = Invoke-MgaRestMethodGet @invokeParam
            if ($Name) { [array]$data = $data | Where-Object displayName -Like $Name }
            if ($Id) { [array]$data = $data | Where-Object Id -Like $Id }

            #endregion query data


            #region output data
            Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData"
            foreach ($output in $data) {
                $outputObject = [MSGraph.AzureAD.Users.User]::new()
                foreach($prop in ($output | Get-Member -MemberType NoteProperty | Where-Object name -notlike "extension_*" | Where-Object name -notlike "@*").Name) {
                    if($output.$prop) {
                        $outputObject.$prop = $output.$prop
                    }
                }
                Write-PSFMessage -Level Debug -Message "Output member '$($outputObject)'." -Tag "OutputData"
                $outputObject
            }
            #endregion output data
        }

    }

    end {
    }
}

function Get-MgaTeamOwner {
    <#
    .SYNOPSIS
        Get owner(s) of a Microsoft Teams Team
 
    .DESCRIPTION
        Get owner(s) of a Microsoft Teams Team(s) via Microsoft Graph API
 
    .PARAMETER InputObject
        A team object where to get owner(s) from.
 
    .PARAMETER Name
        Name filter for the owner(s) to query.
        (Client Side filtering)
 
    .PARAMETER Id
        Id filter for the owners(s) to query.
        (Client Side filtering)
 
    .PARAMETER ResultSize
        The amount of objects to query within API calls to MSGraph.
        To avoid long waitings while query a large number of items, the graph api only
        query a special amount of items within one call.
 
        A value of 0 represents "unlimited" and results in query all items wihtin a call.
        The default is 100.
 
    .PARAMETER Token
        The token representing an established connection to the Microsoft Graph Api.
        Can be created by using New-MgaAccessToken.
        Can be omitted if a connection has been registered using the -Register parameter on New-MgaAccessToken.
 
    .EXAMPLE
        PS C:\> Get-MgaTeamOwner $team
 
        Returns all members from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> $team | Get-MgaTeamOwner -Name "*John*"
 
        Returns any member contains "John" in his name from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
 
    .EXAMPLE
        PS C:\> $team | Get-MgaTeamOwner -ResultSize 5
 
        Retrieves only the first 5 members from team in variable $team.
        Assuming that the variable $team is representing a team queried earlier by Get-MgaTeam
    #>

    [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'Default')]
    [OutputType([MSGraph.AzureAD.Users.User])]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [Alias('Team', 'TeamName', 'TeamID')]
        [MSGraph.Teams.TeamParameter[]]
        $InputObject,

        [Alias('Filter', 'NameFilter', 'FilterName', 'DisplayName')]
        [string]
        $Name,

        [Alias('FilterId', 'IdFilter')]
        [string]
        $Id,

        [Int64]
        $ResultSize = (Get-PSFConfigValue -FullName 'MSGraph.Query.ResultSize' -Fallback 100),

        [MSGraph.Core.AzureAccessToken]
        $Token
    )

    begin {
        $requiredPermission = "Group.Read.All"
        $Token = Invoke-TokenScopeValidation -Token $Token -Scope $requiredPermission -FunctionName $MyInvocation.MyCommand

        #region helper subfunctions
        #endregion helper subfunctions
    }

    process {
        Write-PSFMessage -Level VeryVerbose -Message "Gettings team(s) owner(s) by parameterset $($PSCmdlet.ParameterSetName)" -Tag "ParameterSetHandling"
        Write-PSFMessage -Level Important -Message "This command uses beta version of Microsoft Graph API. Be aware, that this is not supported in production! Use carefully." -Tag "QueryData"

        foreach ($teamItem in $InputObject) {
            #region checking input object type and query message if required
            if ($teamItem.TypeName -like "System.String") {
                $teamItem = Resolve-MailObjectFromString -Object $teamItem -User $User -Token $Token -NoNameResolving -FunctionName $MyInvocation.MyCommand
                if (-not $teamItem) { continue }
            }
            #endregion checking input object type and query message if required


            #region query data
            $invokeParam = @{
                "Field"          = "groups/$($teamItem.Id)/owners"
                "Token"          = $Token
                'UserUnspecific' = $true
                "ResultSize"     = $ResultSize
                "ApiVersion"     = "beta"
                "FunctionName"   = $MyInvocation.MyCommand
            }
            Write-PSFMessage -Level Verbose -Message "Getting team '$($teamItem)' owner(s)" -Tag "QueryData"
            $data = Invoke-MgaRestMethodGet @invokeParam
            if ($Name) { [array]$data = $data | Where-Object displayName -Like $Name }
            if ($Id) { [array]$data = $data | Where-Object Id -Like $Id }

            #endregion query data


            #region output data
            Write-PSFMessage -Level VeryVerbose -Message "Output $($data.Count) objects." -Tag "OutputData"
            foreach ($output in $data) {
                $outputObject = [MSGraph.AzureAD.Users.User]::new()
                foreach($prop in ($output | Get-Member -MemberType NoteProperty | Where-Object name -notlike "extension_*" | Where-Object name -notlike "@*").Name) {
                    if($output.$prop) {
                        $outputObject.$prop = $output.$prop
                    }
                }
                Write-PSFMessage -Level Debug -Message "Output owner '$($outputObject)'." -Tag "OutputData"
                $outputObject
            }
            #endregion output data
        }

    }

    end {
    }
}

<#
This is the configuration file for default values in the module
 
By default, it is enough to have a single one of them,
however if you have enough configuration settings to justify having multiple copies of it,
feel totally free to split them into multiple files.
#>


# module creation defaults - used by module creation framework (PSModuleDevelopment)
Set-PSFConfig -Module 'MSGraph' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'MSGraph' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."

#region Settings inside the module
# Azure Active Directory App
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Application.ClientID' -Value "5e79add2-6288-4d91-bebc-cae920227404" -Initialize -Validation 'string' -Description "Well known ClientID from registered Application in Azure tenant"
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Application.RedirectUrl' -Value "https://login.microsoftonline.com/common/oauth2/nativeclient" -Initialize -Validation 'string' -Description "Redirection URL specified in MS Azure Application portal for the registered application"
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Application.DefaultPermission' -Value @("Mail.ReadWrite.Shared") -Initialize -Validation 'string' -Description "The default permission to consent when getting a token"
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.ApiConnection' -Value "https://graph.microsoft.com" -Initialize -Validation 'string' -Description "The App ID URI of the target web API (secured resource). To find the App ID URI, in the Azure Portal, click Azure Active Directory, click Application registrations, open the application's Settings page, then click Properties."
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.ApiVersion' -Value "v1.0" -Initialize -Validation 'string' -Description "Specifies the API version used to query objects in Microsoft Graph. For more information goto https://docs.microsoft.com/en-us/graph/versioning-and-support"
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Authentiation.IdentityPlatformVersion' -Value "2.0" -Initialize -Validation 'string' -Description "Specifies the endpoint version of the logon platform (Microsoft identity platform) where to connect for logon. For more information goto https://docs.microsoft.com/en-us/azure/active-directory/develop/about-microsoft-identity-platform"
Set-PSFConfig -Module 'MSGraph' -Name 'Tenant.Authentiation.Endpoint' -Value "https://login.microsoftonline.com" -Initialize -Validation 'string' -Description "The URI for authentication and query tokens (access and refresh)"

# web client
Set-PSFConfig -Module 'MSGraph' -Name 'WebClient.UserAgentName' -Value "PowerShellModule.MSGraph.RestClient" -Initialize -Validation 'string' -Description "Name of the user agent in the web client used by module"
Set-PSFConfig -Module 'MSGraph' -Name 'WebClient.UserAgentVersion' -Value "1.1" -Initialize -Validation 'string' -Description "Version for the user agent in the web client used by module"

# command behavior
Set-PSFConfig -Module 'MSGraph' -Name 'Query.ResultSize' -Value 100 -Initialize -Validation integer -Description "Limit of amount of records returned by a function. Use 0 for unlimited."
Set-PSFConfig -Module 'MSGraph' -Name 'Hierarchy.Path.Separator' -Value "\" -Initialize -Validation string -Description "the character used to process hierarchical names (like FullName property on folders) in MSGraph module."

#endregion Settings inside the module

# defining module wide variables

New-Variable -Name msgraph_Token -Scope Script -Visibility Public -Description "Variable for registered token. This is for convinience use with the commands in MSGraph module" -Force


<#
Stored scriptblocks are available in [PsfValidateScript()] attributes.
This makes it easier to centrally provide the same scriptblock multiple times,
without having to maintain it in separate locations.
 
It also prevents lengthy validation scriptblocks from making your parameter block
hard to read.
 
Set-PSFScriptblock -Name 'MSGraph.ScriptBlockName' -Scriptblock {
 
}
#>


<#
# Example:
Register-PSFTeppScriptblock -Name "MSGraph.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
#>



Register-PSFTeppScriptblock -Name "MSGraph.Exchange.Mail.WellKnowFolders" -ScriptBlock { [enum]::GetNames([MSGraph.Exchange.Mail.WellKnownFolder]) | ForEach-Object { (Get-Culture).TextInfo.ToTitleCase( $_ ) } }

Register-PSFTeppScriptblock -Name "MSGraph.Exchange.Category.Colors" -ScriptBlock { [enum]::GetNames([MSGraph.Exchange.Category.ColorName]) }

Register-PSFTeppScriptblock -Name "MSGraph.Core.Permission.Consent.User" -ScriptBlock { @(
    'email',
    'openid',
    'profile',
    'offline_access',
    'Bookings.Manage.All',
    'Bookings.Read.All',
    'Bookings.ReadWrite.All',
    'BookingsAppointment.ReadWrite.All',
    'Calendars.Read',
    'Calendars.Read.Shared',
    'Calendars.ReadWrite',
    'Calendars.ReadWrite.Shared',
    'Contacts.Read',
    'Contacts.Read.Shared',
    'Contacts.ReadWrite',
    'Contacts.ReadWrite.Shared',
    'Device.Command',
    'Device.Read',
    'EAS.AccessAsUser.All',
    'Files.Read',
    'Files.Read.All',
    'Files.Read.Selected',
    'Files.ReadWrite',
    'Files.ReadWrite.All',
    'Files.ReadWrite.AppFolder',
    'Files.ReadWrite.Selected',
    'Financials.ReadWrite.All',
    'Mail.Read',
    'Mail.Read.Shared',
    'Mail.ReadWrite',
    'Mail.ReadWrite.Shared',
    'Mail.Send',
    'Mail.Send.Shared',
    'MailboxSettings.Read',
    'MailboxSettings.ReadWrite',
    'Notes.Create',
    'Notes.Read',
    'Notes.Read.All',
    'Notes.ReadWrite',
    'Notes.ReadWrite.All',
    'Notes.ReadWrite.CreatedByApp',
    'Notifications.ReadWrite.CreatedByApp',
    'People.Read',
    'Sites.Manage.All',
    'Sites.Read.All',
    'Sites.ReadWrite.All',
    'Tasks.Read',
    'Tasks.Read.Shared',
    'Tasks.ReadWrite',
    'Tasks.ReadWrite.Shared',
    'User.Read',
    'User.ReadBasic.All',
    'User.ReadWrite',
    'UserActivity.ReadWrite.CreatedByApp',
    'UserTimelineActivity.Write.CreatedByApp'
) }

<#
# Example:
Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name MSGraph.alcohol
#>


Register-PSFTeppArgumentCompleter -Command New-MgaAccessToken -Parameter "Permission" -Name "MSGraph.Core.Permission.Consent.User"

Register-PSFTeppArgumentCompleter -Command Get-MgaMailFolder -Parameter "Name" -Name "MSGraph.Exchange.Mail.WellKnowFolders"
Register-PSFTeppArgumentCompleter -Command Move-MgaMailFolder -Parameter "DestinationFolder" -Name "MSGraph.Exchange.Mail.WellKnowFolders"
Register-PSFTeppArgumentCompleter -Command New-MgaMailFolder -Parameter "ParentFolder" -Name "MSGraph.Exchange.Mail.WellKnowFolders"
Register-PSFTeppArgumentCompleter -Command Remove-MgaMailFolder -Parameter "Folder" -Name "MSGraph.Exchange.Mail.WellKnowFolders"

Register-PSFTeppArgumentCompleter -Command Get-MgaMailMessage -Parameter "FolderName" -Name "MSGraph.Exchange.Mail.WellKnowFolders"
Register-PSFTeppArgumentCompleter -Command Move-MgaMailMessage -Parameter "DestinationFolder" -Name "MSGraph.Exchange.Mail.WellKnowFolders"

Register-PSFTeppArgumentCompleter -Command Get-MgaExchCategory -Parameter "Color" -Name "MSGraph.Exchange.Category.Colors"
Register-PSFTeppArgumentCompleter -Command New-MgaExchCategory -Parameter "Color" -Name "MSGraph.Exchange.Category.Colors"
Register-PSFTeppArgumentCompleter -Command Set-MgaExchCategory -Parameter "Color" -Name "MSGraph.Exchange.Category.Colors"


New-PSFLicense -Product 'MSGraph' -Manufacturer 'Andreas Bellstedt, Friedrich Weinmann' -ProductVersion $ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2018-08-28") -Text @"
Copyright (c) 2018 Andreas Bellstedt, Friedrich Weinmann
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@

#endregion Load compiled code