O365Essentials.psm1

function Join-UriQuery { 
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together including advanced querying
 
    .DESCRIPTION
    Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url (optional)
 
    .PARAMETER QueryParameter
    Parameters and their values in form of hashtable
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-UrlQuery')]
    [CmdletBinding()]
    param ([parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri,
        [Parameter()][System.Collections.IDictionary] $QueryParameter)
    if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri }
    if ($QueryParameter) {
        $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
        foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) }
    }
    $uriRequest = [System.UriBuilder] $Url
    if ($Collection) { $uriRequest.Query = $Collection.ToString() }
    return $uriRequest.Uri.AbsoluteUri
}
function Remove-EmptyValue { 
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary)
    foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } }
    if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } }
}
function Select-Properties { 
    <#
    .SYNOPSIS
    Allows for easy selecting property names from one or multiple objects
 
    .DESCRIPTION
    Allows for easy selecting property names from one or multiple objects. This is especially useful with using AllProperties parameter where we want to make sure to get all properties from all objects.
 
    .PARAMETER Objects
    One or more objects
 
    .PARAMETER Property
    Properties to include
 
    .PARAMETER ExcludeProperty
    Properties to exclude
 
    .PARAMETER AllProperties
    All unique properties from all objects
 
    .PARAMETER PropertyNameReplacement
    Default property name when object has no properties
 
    .EXAMPLE
    $Object1 = [PSCustomobject] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object2 = [PSCustomobject] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object1, $Object2 -AllProperties
 
    #OR:
 
    $Object1, $Object2 | Select-Properties -AllProperties -ExcludeProperty Name6 -Property Name3
 
    .EXAMPLE
    $Object3 = [Ordered] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object4 = [Ordered] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object3, $Object4 -AllProperties
 
    $Object3, $Object4 | Select-Properties -AllProperties
 
    .NOTES
    General notes
    #>

    [CmdLetBinding()]
    param([Array][Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Objects,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties,
        [string] $PropertyNameReplacement = '*')
    Begin {
        function Select-Unique {
            [CmdLetBinding()]
            param([System.Collections.IList] $Object)
            $New = $Object.ToLower() | Select-Object -Unique
            $Selected = foreach ($_ in $New) {
                $Index = $Object.ToLower().IndexOf($_)
                if ($Index -ne -1) { $Object[$Index] }
            }
            $Selected
        }
        $ObjectsList = [System.Collections.Generic.List[Object]]::new()
    }
    Process { foreach ($Object in $Objects) { $ObjectsList.Add($Object) } }
    End {
        if ($ObjectsList.Count -eq 0) {
            Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.'
            return
        }
        if ($ObjectsList[0] -is [System.Collections.IDictionary]) {
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) { $_.Keys }
                $FirstObjectProperties = Select-Unique -Object $All
            } else { $FirstObjectProperties = $ObjectsList[0].Keys }
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {
                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            } elseif ($Property.Count -gt 0) {
                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_) {
                        $_
                        continue
                    }
                }
            } elseif ($ExcludeProperty.Count -gt 0) {
                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            }
        } elseif ($ObjectsList[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { $FirstObjectProperties = $PropertyNameReplacement } else {
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty }
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) { $_.PSObject.Properties.Name }
                $FirstObjectProperties = Select-Unique -Object $All
            } else { $FirstObjectProperties = $ObjectsList[0].PSObject.Properties.Name }
        }
        $FirstObjectProperties
    }
}
function Join-Uri { 
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together
 
    .DESCRIPTION
    Provides ability to join two Url paths together
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-Url')]
    [cmdletBinding()]
    param([parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri)
    return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/'))
}
function Convert-AzureEnterpriseAppsUserConsent {
    [cmdletbinding()]
    param(
        [Array] $PermissionsGrantPoliciesAssigned,
        [switch] $Reverse
    )
    $StringToProcess = $PermissionsGrantPoliciesAssigned[0]

    if (-not $Reverse) {
        $TranslatePermissions = @{
            'ManagePermissionGrantsForSelf.microsoft-user-default-legacy' = 'AllowUserConsentForApps'
            'ManagePermissionGrantsForSelf.microsoft-user-default-low'    = 'AllowUserConsentForSelectedPermissions'
        }
        if ($StringToProcess -and $TranslatePermissions[$StringToProcess]) {
            $TranslatePermissions[$StringToProcess]
        } else {
            'DoNotAllowUserConsent'
        }
    } else {
        $TranslatePermissions = @{
            'AllowUserConsentForApps'                = 'ManagePermissionGrantsForSelf.microsoft-user-default-legacy'
            'AllowUserConsentForSelectedPermissions' = 'ManagePermissionGrantsForSelf.microsoft-user-default-low'
            'DoNotAllowUserConsent'                  = ''
        }
        $TranslatePermissions[$StringToProcess]
    }
}
function Convert-AzureRole {
    [cmdletbinding()]
    param(
        [string[]] $RoleID,
        [switch] $All
    )
    $Roles = [ordered] @{
        '62e90394-69f5-4237-9190-012177145e10' = 'Global Administrator' # True
        '10dae51f-b6af-4016-8d66-8c2a99b929b3' = 'Guest User' # True
        '2af84b1e-32c8-42b7-82bc-daa82404023b' = 'Restricted Guest User' # True
        '95e79109-95c0-4d8e-aee3-d01accf2d47b' = 'Guest Inviter' # True
        'fe930be7-5e62-47db-91af-98c3a49a38b1' = 'User Administrator' # True
        '729827e3-9c14-49f7-bb1b-9608f156bbb8' = 'Helpdesk Administrator' # True
        'f023fd81-a637-4b56-95fd-791ac0226033' = 'Service Support Administrator' # True
        'b0f54661-2d74-4c50-afa3-1ec803f12efe' = 'Billing Administrator' # True
        'a0b1b346-4d3e-4e8b-98f8-753987be4970' = 'User' # True
        '4ba39ca4-527c-499a-b93d-d9b492c50246' = 'Partner Tier1 Support' # True
        'e00e864a-17c5-4a4b-9c06-f5b95a8d5bd8' = 'Partner Tier2 Support' # True
        '88d8e3e3-8f55-4a1e-953a-9b9898b8876b' = 'Directory Readers' # True
        '9360feb5-f418-4baa-8175-e2a00bac4301' = 'Directory Writers' # True
        '29232cdf-9323-42fd-ade2-1d097af3e4de' = 'Exchange Administrator' # True
        'f28a1f50-f6e7-4571-818b-6a12f2af6b6c' = 'SharePoint Administrator' # True
        '75941009-915a-4869-abe7-691bff18279e' = 'Skype for Business Administrator' # True
        'd405c6df-0af8-4e3b-95e4-4d06e542189e' = 'Device Users' # True
        '9f06204d-73c1-4d4c-880a-6edb90606fd8' = 'Azure AD Joined Device Local Administrator' # True
        '9c094953-4995-41c8-84c8-3ebb9b32c93f' = 'Device Join' # True
        'c34f683f-4d5a-4403-affd-6615e00e3a7f' = 'Workplace Device Join' # True
        '17315797-102d-40b4-93e0-432062caca18' = 'Compliance Administrator' # True
        'd29b2b05-8046-44ba-8758-1e26182fcf32' = 'Directory Synchronization Accounts' # True
        '2b499bcd-da44-4968-8aec-78e1674fa64d' = 'Device Managers' # True
        '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3' = 'Application Administrator' # True
        'cf1c38e5-3621-4004-a7cb-879624dced7c' = 'Application Developer' # True
        '5d6b6bb7-de71-4623-b4af-96380a352509' = 'Security Reader' # True
        '194ae4cb-b126-40b2-bd5b-6091b380977d' = 'Security Administrator' # True
        'e8611ab8-c189-46e8-94e1-60213ab1f814' = 'Privileged Role Administrator' # True
        '3a2c62db-5318-420d-8d74-23affee5d9d5' = 'Intune Administrator' # True
        '158c047a-c907-4556-b7ef-446551a6b5f7' = 'Cloud Application Administrator' # True
        '5c4f9dcd-47dc-4cf7-8c9a-9e4207cbfc91' = 'Customer LockBox Access Approver' # True
        '44367163-eba1-44c3-98af-f5787879f96a' = 'Dynamics 365 Administrator' # True
        'a9ea8996-122f-4c74-9520-8edcd192826c' = 'Power BI Administrator' # True
        'b1be1c3e-b65d-4f19-8427-f6fa0d97feb9' = 'Conditional Access Administrator' # True
        '4a5d8f65-41da-4de4-8968-e035b65339cf' = 'Reports Reader' # True
        '790c1fb9-7f7d-4f88-86a1-ef1f95c05c1b' = 'Message Center Reader' # True
        '7495fdc4-34c4-4d15-a289-98788ce399fd' = 'Azure Information Protection Administrator' # True
        '38a96431-2bdf-4b4c-8b6e-5d3d8abac1a4' = 'Desktop Analytics Administrator' # True
        '4d6ac14f-3453-41d0-bef9-a3e0c569773a' = 'License Administrator' # True
        '7698a772-787b-4ac8-901f-60d6b08affd2' = 'Cloud Device Administrator' # True
        'c4e39bd9-1100-46d3-8c65-fb160da0071f' = 'Authentication Administrator' # True
        '7be44c8a-adaf-4e2a-84d6-ab2649e08a13' = 'Privileged Authentication Administrator' # True
        'baf37b3a-610e-45da-9e62-d9d1e5e8914b' = 'Teams Communications Administrator' # True
        'f70938a0-fc10-4177-9e90-2178f8765737' = 'Teams Communications Support Engineer' # True
        'fcf91098-03e3-41a9-b5ba-6f0ec8188a12' = 'Teams Communications Support Specialist' # True
        '69091246-20e8-4a56-aa4d-066075b2a7a8' = 'Teams Administrator' # True
        'eb1f4a8d-243a-41f0-9fbd-c7cdf6c5ef7c' = 'Insights Administrator' # True
        'ac16e43d-7b2d-40e0-ac05-243ff356ab5b' = 'Message Center Privacy Reader' # True
        '6e591065-9bad-43ed-90f3-e9424366d2f0' = 'External ID User Flow Administrator' # True
        '0f971eea-41eb-4569-a71e-57bb8a3eff1e' = 'External ID User Flow Attribute Administrator' # True
        'aaf43236-0c0d-4d5f-883a-6955382ac081' = 'B2C IEF Keyset Administrator' # True
        '3edaf663-341e-4475-9f94-5c398ef6c070' = 'B2C IEF Policy Administrator' # True
        'be2f45a1-457d-42af-a067-6ec1fa63bc45' = 'External Identity Provider Administrator' # True
        'e6d1a23a-da11-4be4-9570-befc86d067a7' = 'Compliance Data Administrator' # True
        '5f2222b1-57c3-48ba-8ad5-d4759f1fde6f' = 'Security Operator' # True
        '74ef975b-6605-40af-a5d2-b9539d836353' = 'Kaizala Administrator' # True
        'f2ef992c-3afb-46b9-b7cf-a126ee74c451' = 'Global Reader' # True
        '0964bb5e-9bdb-4d7b-ac29-58e794862a40' = 'Search Administrator' # True
        '8835291a-918c-4fd7-a9ce-faa49f0cf7d9' = 'Search Editor' # True
        '966707d0-3269-4727-9be2-8c3a10f19b9d' = 'Password Administrator' # True
        '644ef478-e28f-4e28-b9dc-3fdde9aa0b1f' = 'Printer Administrator' # True
        'e8cef6f1-e4bd-4ea8-bc07-4b8d950f4477' = 'Printer Technician' # True
        '0526716b-113d-4c15-b2c8-68e3c22b9f80' = 'Authentication Policy Administrator' # True
        'fdd7a751-b60b-444a-984c-02652fe8fa1c' = 'Groups Administrator' # True
        '11648597-926c-4cf3-9c36-bcebb0ba8dcc' = 'Power Platform Administrator' # True
        'e3973bdf-4987-49ae-837a-ba8e231c7286' = 'Azure DevOps Administrator' # True
        '8ac3fc64-6eca-42ea-9e69-59f4c7b60eb2' = 'Hybrid Identity Administrator' # True
        '2b745bdf-0803-4d80-aa65-822c4493daac' = 'Office Apps Administrator' # True
        'd37c8bed-0711-4417-ba38-b4abe66ce4c2' = 'Network Administrator' # True
        '31e939ad-9672-4796-9c2e-873181342d2d' = 'Insights Business Leader' # True
        '3d762c5a-1b6c-493f-843e-55a3b42923d4' = 'Teams Devices Administrator' # True
        'c430b396-e693-46cc-96f3-db01bf8bb62a' = 'Attack Simulation Administrator' # True
        '9c6df0f2-1e7c-4dc3-b195-66dfbd24aa8f' = 'Attack Payload Author' # True
        '75934031-6c7e-415a-99d7-48dbd49e875e' = 'Usage Summary Reports Reader' # True
        'b5a8dcf3-09d5-43a9-a639-8e29ef291470' = 'Knowledge Administrator' # True
        '744ec460-397e-42ad-a462-8b3f9747a02c' = 'Knowledge Manager' # True
        '8329153b-31d0-4727-b945-745eb3bc5f31' = 'Domain Name Administrator' # True
        '31392ffb-586c-42d1-9346-e59415a2cc4e' = 'Exchange Recipient Administrator' # True
        '45d8d3c5-c802-45c6-b32a-1d70b5e1e86e' = 'Identity Governance Administrator' # True
        '892c5842-a9a6-463a-8041-72aa08ca3cf6' = 'Cloud App Security Administrator' # True
        '32696413-001a-46ae-978c-ce0f6b3620d2' = 'Windows Update Deployment Administrator' # True
    }
    if ($All) {
        $Roles.Values
    } else {
        foreach ($Role in $RoleID) {
            $RoleName = $Roles[$Role]
            if ($RoleName) {
                $RoleName
            } else {
                $Role
            }
        }
    }
}
function Convert-CompanyType {
    [cmdletbinding()]
    param(
        [string[]] $CompanyType
    )
    $CompanyTypeInformation = [ordered] @{
        '5' = 'Indirect reseller'
        '4' = 'Reseller'
    }
    foreach ($Company in $CompanyType) {
        $CompanyName = $CompanyTypeInformation[$Company]
        if ($CompanyName) {
            $CompanyName
        } else {
            $Company
        }
    }
}
function Convert-ContractType {
    [cmdletbinding()]
    param(
        [string[]] $ContractType
    )
    $ContractTypeInformation = [ordered] @{
        '3' = 'Reseller'
    }
    foreach ($Contract in $ContractType) {
        $ContractName = $ContractTypeInformation[$Contract]
        if ($ContractName) {
            $ContractName
        } else {
            $Contract
        }
    }
}
function Convert-SKUToLicense {
    [cmdletbinding()]
    param(
        [parameter()][string] $SKU
    )

    $ServicePlans = Get-O365AzureLicenses -LicenseSKUID $SKU -ServicePlans -IncludeLicenseDetails
    if ($ServicePlans) {
        $ServicePlans
    }
}
function Find-EnabledServicePlan {
    [cmdletbinding()]
    param(
        [Array] $ServicePlans,
        [Array] $DisabledServicePlans
    )
    $CachePlan = @{}
    foreach ($Plan in $ServicePlans) {
        $CachePlan[$Plan.serviceName] = $Plan
    }

    $Plans = [ordered] @{
        Enabled  = $null
        Disabled = $null
    }

    if ($DisabledServicePlans.Count -gt 0) {
        [Array] $Plans['Enabled'] = foreach ($Plan in $ServicePlans) {
            if ($Plan.serviceName -notin $DisabledServicePlans) {
                $Plan
            }
        }
    } else {
        [Array] $Plans['Enabled'] = $ServicePlans
    }
    [Array] $Plans['Disabled'] = foreach ($Plan in $DisabledServicePlans) {
        $CachePlan[$Plan]
    }
    $Plans
}

function Get-O365PrivateUserOrSPN {
    [cmdletBinding()]
    param(
        [string] $PrincipalID
    )
    $OutputUser = Get-O365User -Id $PrincipalID -WarningAction SilentlyContinue -WarningVariable varWarning
    if ($OutputUser) {
        $OutputUser
    } else {
        $OutputService = Get-O365ServicePrincipal -Id $PrincipalID -WarningAction SilentlyContinue -WarningVariable +varWarning
        if ($OutputService) {
            $OutputService
        }
    }
    if (-not $OutputService -and -not $OutputUser) {
        foreach ($Warning in $VarWarning) {
            Write-Warning -Message $Warning
        }
    }
}
$Script:O365PolicyState = @{
    '2' = 'Report-only'
    '1' = 'Off'
    '0' = 'On' # i think
}
function Connect-O365Admin {
    [cmdletbinding(DefaultParameterSetName = 'Credential')]
    param(
        [parameter(ParameterSetName = 'Credential')][PSCredential] $Credential,
        [parameter(ParameterSetName = 'Headers', DontShow)][alias('Authorization')][System.Collections.IDictionary] $Headers,
        [int] $ExpiresIn = 3600,
        [int] $ExpiresTimeout = 30,
        [switch] $ForceRefresh,
        [alias('TenantID')][string] $Tenant,
        [string] $DomainName,
        [string] $Subscription
    )

    if ($Headers) {
        if ($Headers.ExpiresOnUTC -gt [datetime]::UtcNow -and -not $ForceRefresh) {
            Write-Verbose -Message "Connect-O365Admin - Using cache for connection $($Headers.UserName)"
            return $Headers
        } else {
            # if header is expired, we need to use it's values to try and push it for refresh
            $Credential = $Headers.Credential
            $Tenant = $Headers.Tenant
            $Subscription = $Headers.Subscription
        }
    } elseif ($Script:AuthorizationO365Cache) {
        if ($Script:AuthorizationO365Cache.ExpiresOnUTC -gt [datetime]::UtcNow -and -not $ForceRefresh) {
            Write-Verbose -Message "Connect-O365Admin - Using cache for connection $($Script:AuthorizationO365Cache.UserName)"
            return $Script:AuthorizationO365Cache
        } else {
            $Credential = $Script:AuthorizationO365Cache.Credential
            $Tenant = $Script:AuthorizationO365Cache.Tenant
            $Subscription = $Script:AuthorizationO365Cache.Subscription
        }
    }

    if ($DomainName) {
        Write-Verbose -Message "Connect-O365Admin - Querying tenant to get domain name"
        $Tenant = Get-O365TenantID -DomainName $DomainName
    }

    try {
        $connectAzAccountSplat = @{
            Credential   = $Credential
            ErrorAction  = 'Stop'
            TenantId     = $Tenant
            Subscription = $Subscription
        }
        Remove-EmptyValue -Hashtable $connectAzAccountSplat
        if ($Credential) {
            Write-Verbose -Message "Connect-O365Admin - Connecting to Office 365 using Connect-AzAccount ($($Credential.UserName))"
        } else {
            Write-Verbose -Message "Connect-O365Admin - Connecting to Office 365 using Connect-AzAccount"
        }
        $AzConnect =(Connect-AzAccount@connectAzAccountSplat -WarningVariable warningAzAccount -WarningAction SilentlyContinue )
    } catch {
        if ($_.CategoryInfo.Reason -eq 'AzPSAuthenticationFailedException') {
            if ($Credential) {
                Write-Warning -Message "Connect-O365Admin - Tenant most likely requires MFA. Please drop credential parameter, and just let the Connect-O365Admin prompt you for them."
            } else {
                Write-Warning -Message "Connect-O365Admin - Please provide DomainName or TenantID parameter."
            }
        } else {
            Write-Warning -Message "Connect-O365Admin - Error: $($_.Exception.Message)"
        }
        return
    }

    $Context = $AzConnect.Context

    try {
        Write-Verbose -Message "Connect-O365Admin - Establishing tokens for O365"
        $AuthenticationO365 = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(
            $Context.Account,
            $Context.Environment,
            $Context.Tenant.Id.ToString(),
            $null,
            [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Auto,
            $null,
            'https://admin.microsoft.com'
        )

    } catch {
        Write-Warning -Message "Connect-O365Admin - Authentication failure. Error: $($_.Exception.Message)"
        return
    }
    try {
        Write-Verbose -Message "Connect-O365Admin - Establishing tokens for Azure"
        $AuthenticationAzure = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(
            $Context.Account,
            $Context.Environment,
            $Context.Tenant.Id.ToString(),
            $null,
            [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Auto,
            $null,
            "74658136-14ec-4630-ad9b-26e160ff0fc6"
        )
    } catch {
        Write-Warning -Message "Connect-O365Admin - Authentication failure. Error: $($_.Exception.Message)"
        return
    }

    try {
        Write-Verbose -Message "Connect-O365Admin - Establishing tokens for Graph"
        $AuthenticationGraph = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate(
            $Context.Account,
            $Context.Environment,
            $Context.Tenant.Id.ToString(),
            $null,
            [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Auto,
            $null,
            "https://graph.microsoft.com"
        )
    } catch {
        Write-Warning -Message "Connect-O365Admin - Authentication failure. Error: $($_.Exception.Message)"
        return
    }

    Write-Verbose -Message "Connect-O365Admin - Disconnecting from O365 using Disconnect-AzAccount"
    $null=Disconnect-AzAccount-AzureContext$Context

    $Script:AuthorizationO365Cache = [ordered] @{
        'Credential'          = $Credential
        'UserName'            = $Context.Account
        'Environment'         = $Context.Environment
        'Subscription'        = $Subscription
        'Tenant'              = if ($Tenant) { $Tenant } else { $Context.Tenant.Id }
        'ExpiresOnUTC'        = ([datetime]::UtcNow).AddSeconds($ExpiresIn - $ExpiresTimeout)
        # This authorization is used for admin.microsoft.com
        'AuthenticationO365'  = $AuthenticationO365
        'AccessTokenO365'     = $AuthenticationO365.AccessToken
        'HeadersO365'         = [ordered] @{
            "Content-Type"           ="application/json; charset=UTF-8"
            "Authorization"          ="Bearer $($AuthenticationO365.AccessToken)"
            'X-Requested-With'       = 'XMLHttpRequest'
            'x-ms-client-request-id' = [guid]::NewGuid()
            'x-ms-correlation-id'    = [guid]::NewGuid()
        }
        # This authorization is used for azure stuff
        'AuthenticationAzure' = $AuthenticationAzure
        'AccessTokenAzure'    = $AuthenticationAzure.AccessToken
        'HeadersAzure'        = [ordered] @{
            "Content-Type"           ="application/json; charset=UTF-8"
            "Authorization"          ="Bearer $($AuthenticationAzure.AccessToken)"
            'X-Requested-With'       = 'XMLHttpRequest'
            'x-ms-client-request-id' = [guid]::NewGuid()
            'x-ms-correlation-id'    = [guid]::NewGuid()
        }
        'AuthenticationGraph' = $AuthenticationGraph
        'AccessTokenGraph'    = $AuthenticationGraph.AccessToken
        'HeadersGraph'        = [ordered] @{
            "Content-Type"           ="application/json; charset=UTF-8";
            "Authorization"          ="Bearer $($AuthenticationGraph.AccessToken)"
            'X-Requested-With'       = 'XMLHttpRequest'
            'x-ms-client-request-id' = [guid]::NewGuid()
            'x-ms-correlation-id'    = [guid]::NewGuid()
        }
    }
    $Script:AuthorizationO365Cache
}
function ConvertFrom-JSONWebToken {
    <#
    .SYNOPSIS
    Converts JWT token to PowerShell object allowing for easier analysis.
 
    .DESCRIPTION
    Converts JWT token to PowerShell object allowing for easier analysis.
 
    .PARAMETER Token
    Provide Token to convert to PowerShell object
 
    .PARAMETER IncludeHeader
    Include header as part of ordered dictionary
 
    .EXAMPLE
    ConvertFrom-JSONWebToken -Token .....
 
    .NOTES
    Based on https://www.michev.info/Blog/Post/2140/decode-jwt-access-and-id-tokens-via-powershell
 
    Basically does what: https://jwt.ms/ and https://jwt.io/ do for you online
    #>


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

    # Validate as per https://tools.ietf.org/html/rfc7519
    # Access and ID tokens are fine, Refresh tokens will not work
    if (!$Token.Contains(".") -or !$Token.StartsWith("eyJ")) {
        Write-Warning -Message "ConvertFrom-JSONWebToken - Wrong token. Skipping."
        return
    }

    # Extract header and payload
    $tokenheader, $tokenPayload = $Token.Split(".").Replace('-', '+').Replace('_', '/')[0..1]

    # Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
    while ($tokenheader.Length % 4) {
        $tokenheader += "="
    }
    # Invalid length for a Base-64 char array or string, adding =
    while ($tokenPayload.Length % 4) {
        $tokenPayload += "="
    }
    # Convert header from Base64 encoded string to PSObject all at once
    $header = [System.Text.Encoding]::UTF8.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json

    # Convert payload to string array
    $tokenArray = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($tokenPayload))

    # Convert from JSON to PSObject
    $TokenObject = $tokenArray | ConvertFrom-Json

    # Signature
    foreach ($i in 0..2) {
        $Signature = $Token.Split('.')[$i].Replace('-', '+').Replace('_', '/')
        switch ($Signature.Length % 4) {
            0 { break }
            2 { $Signature += '==' }
            3 { $Signature += '=' }
        }
    }
    $TokenObject | Add-Member -Type NoteProperty -Name "signature" -Value $Signature

    # Convert Expire time to PowerShell DateTime
    $DateZero = (Get-Date -Year 1970 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0 -Millisecond 0)
    $TimeZone = Get-TimeZone
    $UTC = $DateZero.AddSeconds($TokenObject.exp)
    $Offset = $TimeZone.GetUtcOffset($(Get-Date)).TotalMinutes
    $LocalTime = $UTC.AddMinutes($Offset)
    Add-Member -Type NoteProperty -Name "expires" -Value $LocalTime -InputObject $TokenObject

    # Time to Expire
    $TimeToExpire = ($LocalTime - (Get-Date))
    Add-Member -Type NoteProperty -Name "timeToExpire" -Value $TimeToExpire -InputObject $TokenObject

    if ($IncludeHeader) {
        [ordered] @{
            Header = $header
            Token  = $TokenObject
        }
    } else {
        $TokenObject
    }
}
function Get-O365AzureADConnect {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365AzureADConnect -Verbose
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/PassThroughAuthenticationConnectorsBlade
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/GetPasswordSyncStatus"
    $Output3 = Invoke-O365Admin -Uri $Uri -Headers $Headers
    #$Output3 | Format-Table

    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/ADConnectStatus"
    $Output4 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    [PSCustomObject] @{
        passwordSyncStatus               = $Output3
        verifiedDomainCount              = $Output4.verifiedDomainCount              #: 3
        verifiedCustomDomainCount        = $Output4.verifiedCustomDomainCount        #: 2
        federatedDomainCount             = $Output4.federatedDomainCount             #: 0
        numberOfHoursFromLastSync        = $Output4.numberOfHoursFromLastSync        #: 0
        dirSyncEnabled                   = $Output4.dirSyncEnabled                   #: True
        dirSyncConfigured                = $Output4.dirSyncConfigured                #: True
        passThroughAuthenticationEnabled = $Output4.passThroughAuthenticationEnabled #: True
        seamlessSingleSignOnEnabled      = $Output4.seamlessSingleSignOnEnabled      #: True
    }
}
function Get-O365AzureADConnectPTA {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365ModernAuthentication -Verbose
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/PassThroughAuthenticationConnectorsBlade
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/PassThroughAuthConnectorGroups"
    $Output1 = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output1
}
function Get-O365AzureADConnectSSO {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365AzureADConnect -Verbose
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/PassThroughAuthenticationConnectorsBlade
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/GetSeamlessSingleSignOnDomains"
    $Output2 = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output2
}
function Get-O365AzureADRoles {
    [cmdletBinding()]
    param(

    )

    #https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments&$filter=roleDefinitionId eq ‘<object-id-or-template-id-of-role-definition>’


    #$Uri = 'https://main.iam.ad.ext.azure.com/api/Roles/User/e6a8f1cf-0874-4323-a12f-2bf51bb6dfdd/RoleAssignments?scope=undefined'
    $Uri = 'https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions'
    <#
    $QueryParameter = @{
        '$Select' = $Property -join ','
        '$filter' = $Filter
        '$orderby' = $OrderBy
    }
    #>

    <#
    GET https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions?$filter=DisplayName eq 'Conditional Access Administrator'&$select=rolePermissions
    #>

    Write-Verbose -Message "Get-O365AzureADRoles - Getting all Azure AD Roles"
    $Script:AzureADRolesList = [ordered] @{}
    $Script:AzureADRolesListReverse = [ordered] @{}
    $RolesList = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter -Method GET
    $Script:AzureADRoles = $RolesList
    foreach ($Role in $RolesList) {
        $Script:AzureADRolesList[$Role.id] = $Role
        $Script:AzureADRolesListReverse[$Role.displayName] = $Role
    }

    $RolesList
}

<#
Invoke-WebRequest -Uri "https://main.iam.ad.ext.azure.com/api/Roles/User/e6a8f1cf-0874-4323-a12f-2bf51bb6dfdd/RoleAssignments?scope=undefined" `
-Method "OPTIONS" `
-Headers @{
"Accept"="*/*"
  "Access-Control-Request-Method"="GET"
  "Access-Control-Request-Headers"="authorization,content-type,x-ms-client-request-id,x-ms-client-session-id,x-ms-effective-locale"
  "Origin"="https://portal.azure.com"
  "User-Agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
  "Sec-Fetch-Mode"="cors"
  "Sec-Fetch-Site"="same-site"
  "Sec-Fetch-Dest"="empty"
  "Accept-Encoding"="gzip, deflate, br"
  "Accept-Language"="en-US,en;q=0.9,pl;q=0.8"
}
#>



<#
 
GET https://admin.exchange.microsoft.com/beta/RoleGroup? HTTP/1.1
Host: admin.exchange.microsoft.com
Connection: keep-alive
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Microsoft Edge";v="92"
x-ms-mac-hostingapp: M365AdminPortal
AjaxSessionKey: x5eAwqzbVehBOP7QHfrjpwr9eYtLiHJt7TZFj0uhUMUPQ2T7yNdA7rEgOulejHDHYM1ZyCT0pgXo96EwrfVpMA==
x-adminapp-request: /rbac/exchange
sec-ch-ua-mobile: ?0
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiI0OTdlZmZlOS1kZjcxLTQwNDMtYThiYi0xNGNmNzhjNGI2M2IiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jZWIzNzFmNi04NzQ1LTQ4NzYtYTA0MC02OWYyZDEwYTlkMWEvIiwiaWF0IjoxNjMwMjYwMjMxLCJuYmYiOjE2MzAyNjAyMzEsImV4cCI6MTYzMDI2NDEzMSwiYWNyIjoiMSIsImFpbyI6IkFWUUFxLzhUQUFBQXBtQ1F1b2lIR3lpYTd0dFB0czFZOEpIWUpleTB1Zndzb2oycUFvSEJKWjhQclowWlJONmhQSW5BblZHRld2cXp1R0xtbXNyS1Vaak12ZVBwNDJsQXhHY0d1bk5ZNTNNMmdWbE9uSXRhcHBrPSIsImFtciI6WyJyc2EiLCJtZmEiXSwiYXBwaWQiOiIwMDAwMDAwNi0wMDAwLTBmZjEtY2UwMC0wMDAwMDAwMDAwMDAiLCJhcHBpZGFjciI6IjIiLCJkZXZpY2VpZCI6IjNhZTIyNzI2LWRmZDktNGFkNy1hODY1LWFhMmI1MWM2ZTBmZiIsImZhbWlseV9uYW1lIjoiS8WCeXMiLCJnaXZlbl9uYW1lIjoiUHJ6ZW15c8WCYXciLCJpcGFkZHIiOiI4OS43Ny4xMDIuMTciLCJuYW1lIjoiUHJ6ZW15c8WCYXcgS8WCeXMiLCJvaWQiOiJlNmE4ZjFjZi0wODc0LTQzMjMtYTEyZi0yYmY1MWJiNmRmZGQiLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtODUzNjE1OTg1LTI4NzA0NDUzMzktMzE2MzU5ODY1OS0xMTA1IiwicHVpZCI6IjEwMDMwMDAwOTQ0REI4NEQiLCJyaCI6IjAuQVM4QTluR3p6a1dIZGtpZ1FHbnkwUXFkR2dZQUFBQUFBUEVQemdBQUFBQUFBQUF2QUM4LiIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInNpZCI6ImJkYjU3MmNiLTNkMzgtNGZlZi1iNjg2LTlmODhjNWRkNWQyNSIsInN1YiI6ImRranZjSlpIWjdjWkZPbnlSZkxZaDVLeHBUalVWdEVBLTVNSl81aF9GLWMiLCJ0aWQiOiJjZWIzNzFmNi04NzQ1LTQ4NzYtYTA0MC02OWYyZDEwYTlkMWEiLCJ1bmlxdWVfbmFtZSI6InByemVteXNsYXcua2x5c0Bldm90ZWMucGwiLCJ1cG4iOiJwcnplbXlzbGF3LmtseXNAZXZvdGVjLnBsIiwidXRpIjoiekxXUTdvUmc4ay0yVmlJV1dQNG1BQSIsInZlciI6IjEuMCIsIndpZHMiOlsiNjJlOTAzOTQtNjlmNS00MjM3LTkxOTAtMDEyMTc3MTQ1ZTEwIiwiYjc5ZmJmNGQtM2VmOS00Njg5LTgxNDMtNzZiMTk0ZTg1NTA5Il19.nzALEBEAAQBJddeeyt7Gn5sgy7y1Z1z_jfpLdjsPjgNSEOlHLPHqeyOx9QuHaEywK6es2pobYfhFtUvx1d09nz0qBI0b1wIRMX2W2-XaQOmg0FRTDQvTcC9d4Kum_hXmpTt8WgIpjKLKE0wmW8ZtsHbmh-JH3m9Y8j-9zktiRFtNbEyEa1uCTD7Wph9Ow_PAc6M9mWrERCb_XzaYDuwZWbfA_Ls2Bv8MGQsfkQh9RBsa-TgeuU1hhhGgcSaHPFAytJVQBq6QuMdqnO1pCevECf_OI2K54CcpISAUAPXW_gZXcj1waXzRRQfm85vCCh14oXvEj-Q94RsSq_5c_8cEFA
client-request-id: 64d0ca10-08f4-11ec-ad6e-f9fb25a685f4
Accept: application/json, text/plain, */*
x-ms-mac-version: host-mac_2021.8.19.4
x-portal-routekey: weu
x-ms-mac-appid: 86d5ab1a-7f52-418c-b62d-a33841f2c949
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84
x-ms-mac-target-app: EAC
Origin: https://admin.microsoft.com
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://admin.microsoft.com/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,pl;q=0.8
 
 
 
GET https://admin.microsoft.com/admin/api/rbac/deviceManagement/roles HTTP/1.1
Host: admin.microsoft.com
Connection: keep-alive
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Microsoft Edge";v="92"
x-ms-mac-hostingapp: M365AdminPortal
AjaxSessionKey: x5eAwqzbVehBOP7QHfrjpwr9eYtLiHJt7TZFj0uhUMUPQ2T7yNdA7rEgOulejHDHYM1ZyCT0pgXo96EwrfVpMA==
x-adminapp-request: /rbac/deviceManagement
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84
Accept: application/json, text/plain, */*
x-ms-mac-version: host-mac_2021.8.19.4
x-portal-routekey: weu
x-ms-mac-appid: 86d5ab1a-7f52-418c-b62d-a33841f2c949
x-ms-mac-target-app: MAC
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://admin.microsoft.com/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,pl;q=0.8
Cookie: MC1=GUID=480c128a5ba04faea7df151a53bdfa9a&HASH=480c&LV=202107&V=4&LU=1627670649689; x-portal-routekey=weu; p.BDId=00ab552e-0bd2-44f6-afb9-cbec94cb4051; s.AjaxSessionKey=x5eAwqzbVehBOP7QHfrjpwr9eYtLiHJt7TZFj0uhUMUPQ2T7yNdA7rEgOulejHDHYM1ZyCT0pgXo96EwrfVpMA%3D%3D; s.cachemap=22; s.BrowserIDe6a8f1cf-0874-4323-a12f-2bf51bb6dfdd=00ab552e-0bd2-44f6-afb9-cbec94cb4051; s.classic=False; s.CURedir=True; s.DisplayCulture=en-US; s.MFG=True; p.UtcOffset=-120; market=US; mslocale={'u':'pl-pl'}; LPVID=JjZDkxOGFjNzI3ZTFlZmY5; s.Cart={"BaseOffers":null,"Frequency":0,"IWPurchaseUserId":null,"PromotionCodes":null,"IsOfferTransition":false}; s.InNewAdmin=True; at_check=true; p.FirstLoginDateTimeUtc=id=-172832015&value=Oct_14_2015; s.ImpressionId=9c5222af-0f0a-4464-886a-ebc7eee1b188; p.FirstBillingYear=id=-172832015&value=2015; s.DefaultBillingMonth=08/20/2021 20:04:06; s.DCLoc=weuprod; p.TenantCulture=ceb371f6-8745-4876-a040-69f2d10a9d1a::pl-PL; mbox=PC#7383f384d21f43ef9a0d9d5c273578ed.37_0#1664248950|session#020faa6548144c6486e3597fd3298e30#1630064111; LPSID-60270350=HKOY_Iw3QjSJ9ijUKJt52Q; s.SessID=8798a6f9-b245-4f5e-99ed-1b78809d75a1; RootAuthToken=AwAAABoxMS8yNi8yMDIxIDE5OjA1OjU2ICswMDowMOoLMC5BUzhBOW5HenprV0hka2lnUUdueTBRcWRHZ1lBQUFBQUFQRVB6Z0FBQUFBQUFBQXZBQzguQWdBQkFBQUFBQUQtLURMQTNWTzdRcmRkZ0pnN1dldnJBZ0RzX3dRQTlQOHJfczBGMDYxZ053MTBrZllsVmtYOHZnYVZIbWFUWUVDbXltT2dzMWlGTFRsQXZ4VS1lYjdScjg2U2QwYXZSY05tNGpIcWhlMDBrYmJuWUh3NEtURnVHalN4cklQRTZjamlUUXJKYmRMMXNzcVpBVTZpUE94eVM1aHQzNEN0M1p0bWV1Y1BXaWNsZFdUNmlwVDJtT3JNS2RDaVZLUS1iRGctSHRjUjc3R094eU1hM2pIclBWOXZkOVFqdEdBY1g0azNqalZtRHJhODVFWFRKRjk3TFFEVUxGcWw1SUhyTy1ROHVfQ1RneUFjeHdjVml0anNBRllyUVFwTTFZM3hJRTFxcF9BXzZzeUZuRUFQVm9kUDIzSkUwLWtHaWNvYUowOXhRSlpvMEdTM2IwSzJtUWdFMTYybUUzRGdDQ3lna01qRjlBNko2b0otY09pZ25JOFVLNGg0MnAzVDlSeEdEbVZrTEM4LVJUbHVBUkVNS1JmTElsbFNXZVota1JHU1pZVExLY3IxUG5NN2pVSjBGZDZUODl5LTE1cFVSaWdFdHZKLTJtUms0S2s0ejUwTUtEUzIwejQzMHV3b19Ra1N6el81QnpyeFU5dnY1aGZGai1aOU5TSC16eEtKWHpaUUxua2toMElMd2NVc0xQVmdDVndPa3ZYTlRLazdNWmNUdHcyLVBOVFV2bTJha0p0ZjNQM3hIZExNX3RzdFlZUmxjVUlSOVBWWGFOeXpRTWlvX0kxU1lCanh6bUtpM2tfdVBvdXd1bzYycDltZlFOeUlrOWotX2ctQ2RlSVA4WVBsOHRnekhNMXJWTGlBWjdLUVlDZHpBNXRBMll5LVFFZEYyYi1VcDZqblFmeExoRnJBM3ZleWpRck1MUmJCdVVGVUxscUVMODZSRE16eld1eENoSGh1eEdzSTZaYnFPek9BdUJBSHpjRGxuNHN1MEpEQ3hmc3hidWhocTk4RHNHdWQ4MnhJNU1zYWtEZlZfYUN4ZXV0NzRPR1A2SnkyVGtaLVRJNWcyc2xpM2hEOW14VWRVNmVNdG1HQTVXeWs4dG9JaG1oMXVtMWNNVTBsblpFOUhOZDJKMzJHbk1LaU5Mejg4dktfeW5Va1llY2NwOEcwNUR4aUtpSGkzVUZqNm01eXFBSnVneFRTYTZ6SHBGWHF0SVFfNXZjTHJjM2JhR1paMm4xWjdBTEhZd3hxN3NSSmFNUTROZnFpRnNlT2pqR1JaNjI1VU5kdWxFTGJaLUp1aFpiS05uTG1wQlB5UkNSMEp2czlpekdRRU1ReXhhMzJKWHEzaGduQi1TcGd5N1lLS1dmYXN2MmVhV0hibUctR1hxQ2N4THZKbUV4WHM5VXVBd2NzLU1pc19wUmNzNTdlWmxHZ0xPTlpNUGxvNGQ5Vmh1b0tLanQ4dHdlRDA1VXZ5VGIzaFhjSjFBVG41aVhQampwaEFVaEhPNS01ZXg0cDkyakVmcy13SUhRV2ZURE1aZy1Ca0diVU85LUQtd0NSVExqVm1QV0k2TUFpM0h3cWxvZXZ2M3doWE80bm9NQmVMQlRsNHRiT2QyZXZocXlPaTNudHpNTzVBMFoxbzFDZ1NCMkozVkxRbFpDR2ZocHdHd1kwT3JGb25kVExIcG5LUnBLOE1YdkRLdldSd0tqUl9CSEVPU0IxNlVQOHg2SERZV1BUaGRWRDJFamVtbWpZWmgxaVF0cnZobjBFYTJiRE1yekVHcjFNTlFROGlPVENnLXlVWk1YeVVobXJYMklra05rVExZWWYzRU1ndHBGRUhDWHRTZWpueVphZ1JvcGh0M05yUTJ2MWFXOFE4Q1pfR21XcGs5dkE2MXR4MUhKZHExY3FldE1GM3FpbFAxSTJjd09RZFdNZy1OT2cA; s.LoginUserTenantId=kyHaNehhz9jpR+09ZPKS4DynUHwzw7PquEHQY+SZE6vRWhg+ZenTYDg29pApIbkUamgN9MVhZ/VbADv3Wr2Xnn3vQCRp3hHGvLU4EDcKBxLdi/J1UCSJ5YS6JobJ+hPsanTiHrdOwR5fSMI4rt1cJg==; UserIndex=H4sIAAAAAAAEAGNkYGBgBGI2IGYCsfWBBINkQVFVam5lcU5iuV52TmWxQ2pZfklqsl5BDjNQVtjIwMhQ18BC18gyxNDCysDUytSCBaSNFYTdEnOKU0HGqSSlJJmaGyUn6RqnGFvomqSlpukmmVmY6VqmWVgkm6akmKYYmYK0AQDFS8PlhAAAAA%3D%3D; OIDCAuthCookie=%2BSKNwKbOp3tUWr2%2BSTWrgME8BQoKkh7P55ishMUl3EwwalLmRnorz031%2FWXRh2gszg0uE20Nfdak8qB1vtHFOz%2FF24zwiQa0THjlt6pnBbz9vyhA4iuJNzvwt3XjSmId3Da9X8P4nQ%2FUJE%2BssHTASvNOEnPrMWvrBm1z0222f3GgiWQ2v9ArrbeXOxWvV8Me%2BUPnQ%2FEDui%2B940hO6htSDcG3h46GZJBbFSysbtE5dgQgPhixil29dQE7npcsCycLBgv%2FwypJXh%2BKq5mD%2BpfJwtNbDmvuxz9eQYZUBPWvriBHva6on%2FRXp19xAX8K%2BMwukPVYtCbqeaLP5LCK%2B1pQAFFa4GtKOY1OxVmIUcTSg88Jf0DGWYkR8CzFINgWxNhsVXRV%2BIWjz2OF6irsv%2F3L18zNFxluVlL41uzho5gqlI%2BTmgwtO%2FtWwMDqfZkdVYaufr%2B6DF6alJHFTGEb67sTmlMGeBI1w%2BeHc9Z3alFqLqcBVxg8XB88pUxzF6Dj7CGySByFC2lg%2FZaZeNgFx4BYYUa4o2rYpWhjVhYcXxixSOmFaqZhEEOCdrgB5qoTdoGMPCpoj22C9g6yow1l51GANTK9ujTRGS5LYLFA7R%2BSIcQNM50zDU1wAgoAl%2BnWQUjzK5D9XlBMhSovq7Dd4hXFW%2BnsQp2xKJL1AcE89FVKhlC3LKiwNHKSmDz2mlvYHyVRasm1jbel1BY0dKd%2F1ZMd5aKg94GEXeMdwpyyyg573HuFbCnPBd4TYdeMPg6siaMj%2Bwt%2BcZfZGbm6A9xfaq82vzUP3AU0lmz%2BYxaPT3e4fqmQVNxw0FvfPoIjy3SHaQryqseAP0LVwC6GXFOH4yEGtC63Y%2F%2FVOaE0LXbhhN10ejkQbwZGDtpUiO3%2FBihlUTVAEvYlWEnNd1Mjnr1uRl0JPknEUsbFe4gQNi7UIZo4T7vjDeNGom53bp%2BFryaNb9jCQi3jp1f9CU2xli%2B6pH%2B%2BuFvnODrDE5tJUHE3v13LljzGCbLXO%2B91K17KzIfiAoKAYnZPWNsFvgp2iUPbNRqax%2FBBtF7Zv%2ByofRex3OxXAR1kQCUcmxzItYeLBMNkTKY4B6L8w84U8Cmem%2Fnets2xdzNcYnu30qJkwHIckG7M5A4bpObrscZQ34XOiZ3%2FaLb3nAt8GhOgRe51XbzqVX86NeE9iDBhis%2FBG0JY2Ux3LZO%2B1FwKMjLO2a60OnJIRgORbjSaJV3aJiCkBlbpQ4PX5zXji41h4wSlVzYNsy2QlGJVpDfqbfqWl2DAH5JQKobBmlcp7bn3l6GGXR0XUcJJ6Qi4ZmXzofMLlc6zRhKe15cBp0zmzAlTd4%2BH6kbcduITep6h3NdjDmwFoTz96XY%2BzCE3HxgL1zrVW8qr6WYqSGbaaSqVMNKoM6Z33CRB%2BFUoVpZjHRl2kAxVdvRc3zLSI10M23PELrDur56TDDpgfi2ERY1DjNnS8BaucCs5Rqh35QQPLGmumMRprtrURFitfRlLgl7ZSyOW62ScyxxclityxBeY8NA%2Fi8IPFGrWSSrSVjAtTsJVjRUX5GHIANuZL9YImsVnrShvbyrbxMmSxfJ45pAo8mqX%2FGwnOg7V8TabzvYWuWZvUwpM%2BFktbNaQ960iRoR00UYI0IhC4hnoAAnlKeguUGq8aHuEUywllI%2FwYyjlRCXkx7znLCMj%2FuG7x7acGAStDg%2F97Q8ImojZWT9y9oD6QIPQLI7%2B4vqxBht2ZHxZMxr2WsoAUn1cB7WvPtIyJA43T36AL%2F64X0rg8Kj0nMsC3eQzoJGaWB9XSJzogLtCZAQ1W5%2BLPCIpsWL3IsL9J2gjivl%2BKNH8kDxckxpTFB69Rkau0%2BgjXXiyHEQEd6%2FDtOeRMI3MOWg%2FGzjVbVNxMJock%2B%2FpoAf%2FPgtkV8w%3D; s.DmnHQT=08/29/2021 18:06:00; s.DmnRQT=08/29/2021 18:06:07; s.DmnSOQT=08/29/2021 18:06:07; p.LastLoginDateTimeUtc=Aug_29_2021_18_06_00; MicrosoftApplicationsTelemetryDeviceId=f7e3a469-8044-4a21-ad55-69ce7f6b4086; MicrosoftApplicationsTelemetryFirstLaunchTime=2021-08-29T18:10:03.226Z
 
 
#>

function Get-O365AzureADRolesMember {
    [cmdletBinding(DefaultParameterSetName = 'Role')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Role')][Array] $RoleName,

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

        [Parameter(ParameterSetName = 'Role')]
        [Parameter(ParameterSetName = 'Filter')]
        [Parameter(ParameterSetName = 'All')]
        [string[]] $Property,

        [Parameter(ParameterSetName = 'Role')]
        [Parameter(ParameterSetName = 'Filter')]
        [Parameter(ParameterSetName = 'All')]
        [string] $OrderBy,

        [Parameter(ParameterSetName = 'All')][switch] $All
    )
    $Uri = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments"
    $QueryParameter = @{
        '$Select'  = $Property -join ','
        '$orderby' = $OrderBy
    }

    $RolesList = [ordered] @{}
    if ($RoleName -or $All) {
        # in case user wanted all roles, we get it to him
        if ($All) {
            # we either use cache, or we ask for it
            if (-not $Script:AzureADRoles) {
                $RoleName = (Get-O365AzureADRoles).displayName
            } else {
                $RoleName = $Script:AzureADRoles.displayName
            }
        }
        # We want to get one or more roles at the same time
        foreach ($Role in $RoleName) {
            $RoleID = $null
            # We find the ID based on the cache or we ask Graph API to provide the list the first time
            if ($Script:AzureADRolesListReverse) {
                $TranslatedRole = $Script:AzureADRolesListReverse[$Role]
            } else {
                $null = Get-O365AzureADRoles
                $TranslatedRole = $Script:AzureADRolesListReverse[$Role]
            }
            if ($TranslatedRole) {
                # Once we have ID we query graph API
                $RoleID = $TranslatedRole.id
                $QueryParameter['$filter'] = "roleDefinitionId eq '$RoleID'"
            } else {
                Write-Warning -Message "Get-O365AzureADRolesMember - Couldn't gather roles because the ID translation didn't work for $Role"
                continue
            }
            Remove-EmptyValue -Hashtable $QueryParameter
            # We query graph API
            Write-Verbose -Message "Get-O365AzureADRolesMember - requesting role $Role ($RoleID)"
            $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter -Method GET
            # if we asked for just one role we return the results directly
            if ($RoleName.Count -eq 1) {
                Write-Verbose -Message "Get-O365AzureADRolesMember - requesting users for $Role ($RoleID)"
                foreach ($User in $Output) {
                    Get-O365PrivateUserOrSPN -PrincipalID $User.principalId
                }
            } else {
                # if we asked for more than one role we add the results to the list
                Write-Verbose -Message "Get-O365AzureADRolesMember - requesting users for $Role ($RoleID)"
                $RolesList[$Role] = foreach ($User in $Output) {
                    Get-O365PrivateUserOrSPN -PrincipalID $User.principalId
                }
            }
        }
        if ($RoleName.Count -gt 1) {
            # if we asked for more than one role we return the list
            $RolesList
        }
    } elseif ($Filter) {
        $QueryParameter['$filter'] = $Filter
        Remove-EmptyValue -Hashtable $QueryParameter
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter -Method GET
        foreach ($User in $Output) {
            Get-O365PrivateUserOrSPN -PrincipalID $User.principalId
        }
    }


}

$Script:AzureRolesScriptBlock = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    #Convert-AzureRole -All | Where-Object { $_ -like "*$wordToComplete*" }
    if (-not $Script:AzureADRoles) {
        $AzureRoles = Get-O365AzureADRoles
    } else {
        $AzureRoles = $Script:AzureADRoles
    }
    ($AzureRoles | Where-Object { $_.displayName -like "*$wordToComplete*" }).displayName
}

Register-ArgumentCompleter -CommandName Get-O365AzureADRolesMember -ParameterName RoleName -ScriptBlock $Script:AzureRolesScriptBlock

<#
    https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments&$filter=roleDefinitionId eq ‘<object-id-or-template-id-of-role-definition>’
    #>


#https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments&$filter=roleDefinitionId eq ‘<object-id-or-template-id-of-role-definition>’


#$Uri = 'https://main.iam.ad.ext.azure.com/api/Roles/User/e6a8f1cf-0874-4323-a12f-2bf51bb6dfdd/RoleAssignments?scope=undefined'

<#
GET https://graph.microsoft.com/beta/rolemanagement/directory/roleAssignments?$filter=principalId eq '55c07278-7109-4a46-ae60-4b644bc83a31'
 
 
GET https://graph.microsoft.com/beta/groups?$filter=displayName+eq+'Contoso_Helpdesk_Administrator'
 
GET https://graph.microsoft.com/beta/roleManagement/directory/roleAssignments?$filter=principalId eq
#>


<#
Invoke-WebRequest -Uri "https://api.azrbac.mspim.azure.com/api/v2/privilegedAccess/aadroles/roleAssignments?`$expand=linkedEligibleRoleAssignment,subject,scopedResource,roleDefinition(`$expand=resource)&`$count=true&`$filter=(roleDefinition/resource/id%20eq%20%27ceb371f6-8745-4876-a040-69f2d10a9d1a%27)+and+(roleDefinition/id%20eq%20%275d6b6bb7-de71-4623-b4af-96380a352509%27)+and+(assignmentState%20eq%20%27Eligible%27)&`$orderby=roleDefinition/displayName&`$skip=0&`$top=10" -Headers @{
"x-ms-client-session-id"="3049c4c42d944f68bb7423154f7a1da5"
  "Accept-Language"="en"
  "Authorization"="Bearer ."
  "x-ms-effective-locale"="en.en-us"
  "Accept"="application/json, text/javascript, */*; q=0.01"
  #"Referer"=""
  "x-ms-client-request-id"="b0a543fc-ca4c-4ac6-aef6-5ceb09ad9003"
  "User-Agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
}
#>

function Get-O365AzureConditionalAccess {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $Details
    )
    #$Uri = 'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies'
    # Need to figure out scopes and use graph instead. But till then...
    #$Uri = 'https://main.iam.ad.ext.azure.com/api/Policies/Policies?top=10&nextLink=null&appId=&includeBaseline=true'
    # "https://main.iam.ad.ext.azure.com/api/Policies/7eac83fb-856b-45bf-9896-4fc78ea686f1"

    # move it later on
    $Script:O365PolicyState = @{
        '2' = 'Report-only'
        '1' = 'Off'
        '0' = 'On' # i think
    }

    $QueryParameters = @{
        top             = 10
        nextLink        = $null
        #appID = ''
        includeBaseline = $true
    }
    $Uri = 'https://main.iam.ad.ext.azure.com/api/Policies/Policies'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameters
    if ($Output.items) {
        foreach ($Policy in $Output.items) {
            if (-not $Details) {
                [PSCustomObject] @{
                    PolicyId       = $Policy.policyId         #: 7eac83fb-856b-45bf-9896-4fc78ea686f1
                    PolicyName     = $Policy.policyName       #: Guest Access Policy 1
                    ApplyRule      = $Policy.applyRule        #: False
                    PolicyState    = $O365PolicyState[$Policy.policyState.ToString()]      #: 1
                    UsePolicyState = $Policy.usePolicyState   #: True
                    BaselineType   = $Policy.baselineType     #: 0
                    CreatedDate    = $Policy.createdDateTime  #: 11.09.2021 09:02:21
                    ModifiedDate   = $Policy.modifiedDateTime #: 11.09.2021 17:38:21
                }
            } else {
                $PolicyDetails = Get-O365AzureConditionalAccessPolicy -PolicyID $Policy.policyId
                if ($PolicyDetails) {
                    $PolicyDetails
                }
            }
        }
    }
}
function Get-O365AzureConditionalAccessClassic {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/ClassicPolicies'

    $QueryParameters = @{
        top      = 10
        nextLink = $null
        filter   = 1
    }

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameters
    if ($Output.items) {
        $Output.items
    }
}
function Get-O365AzureConditionalAccessLocation {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://graph.microsoft.com/beta/conditionalAccess/namedLocations'

    #?`$filter=&`$orderby=displayName&`$skip=0&`$top=10&`$count=true

    $QueryParameters = @{
        top     = 10
        skip    = 0
        orderby = 'displayName'
        filter  = ''
        count   = 'true'
    }

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameters
    if ($Output) {
        $Output
    }
}
function Get-O365AzureConditionalAccessPolicy {
    [cmdletbinding()]
    param(
        [parameter(ParameterSetName = 'PolicyID')]
        [parameter(ParameterSetName = 'PolicyName')]
        [alias('Authorization')][System.Collections.IDictionary] $Headers,

        [parameter(Mandatory, ParameterSetName = 'PolicyID')][string] $PolicyID,
        [parameter(Mandatory, ParameterSetName = 'PolicyName')][string] $PolicyName
    )
    # move it later on
    $Script:O365PolicyState = @{
        '2' = 'Report-only'
        '1' = 'Off'
        '0' = 'On' # i think
    }


    if ($PolicyID) {
        $Uri = "https://main.iam.ad.ext.azure.com/api/Policies/$PolicyID"
    } elseif ($PolicyName) {
        $FoundPolicy = $null
        $Policies = Get-O365AzureConditionalAccess -Headers $Headers
        foreach ($Policy in $Policies) {
            if ($Policy.policyName -eq $PolicyName) {
                $FoundPolicy = $Policy.policyId
                break
            }
        }
        if ($null -ne $FoundPolicy) {
            $Uri = "https://main.iam.ad.ext.azure.com/api/Policies/$FoundPolicy"
        } else {
            Write-Warning -Message "Get-O365AzureConditionalAccessPolicy - No policy with name $PolicyName"
            return
        }
    } else {
        Write-Warning -Message "Get-O365AzureConditionalAccessPolicy - No policy ID or name specified"
        return
    }

    $PolicyDetails = Invoke-O365Admin -Uri $Uri -Headers $Headers #-QueryParameter $QueryParameters
    if ($PolicyDetails) {
        [PSCustomObject] @{
            PolicyId               = $PolicyDetails.policyId         #: 7eac83fb-856b-45bf-9896-4fc78ea686f1
            PolicyName             = $PolicyDetails.policyName       #: Guest Access Policy 1
            ApplyRule              = $PolicyDetails.applyRule        #: False
            PolicyState            = $Script:O365PolicyState[$PolicyDetails.policyState.ToString()]      #: 1
            UsePolicyState         = $PolicyDetails.usePolicyState   #: True
            BaselineType           = $PolicyDetails.baselineType     #: 0
            CreatedDate            = $PolicyDetails.createdDateTime  #: 11.09.2021 09:02:21
            ModifiedDate           = $PolicyDetails.modifiedDateTime #: 11.09.2021 17:38:21
            #users = $PolicyDetails.users # # : @{allUsers=2; included=; excluded=}
            Users                  = $PolicyDetails.usersV2                # # : @{allUsers=2; included=; excluded=}
            #servicePrincipals = $PolicyDetails.servicePrincipals # # : @{allServicePrincipals=1; included=; excluded=; filter=; includeAllMicrosoftApps=False; excludeAllMicrosoftApps=False; userActions=; stepUpTags=}
            ServicePrincipals      = $PolicyDetails.servicePrincipalsV2    # # : @{allServicePrincipals=1; included=; excluded=; filter=; includedAppContext=; shouldIncludeAppContext=False}
            controls               = $PolicyDetails.controls               # # : @{controlsOr=True; blockAccess=False; challengeWithMfa=True; compliantDevice=False; domainJoinedDevice=False; approvedClientApp=False; claimProviderControlIds=System.Object[]; requireCompliantApp=False; requirePasswordChange=False; requiredFederatedAuthMethod=0}
            sessionControls        = $PolicyDetails.sessionControls        # # : @{appEnforced=False; cas=False; cloudAppSecuritySessionControlType=0; signInFrequencyTimeSpan=; signInFrequency=0; persistentBrowserSessionMode=0; continuousAccessEvaluation=0; resiliencyDefaults=0; secureSignIn=False}
            conditions             = $PolicyDetails.conditions             # # : @{minUserRisk=; minSigninRisk=; devicePlatforms=; locations=; namedNetworks=; clientApps=; clientAppsV2=; time=; deviceState=}
            clientApplications     = $PolicyDetails.clientApplications     # # : @{allServicePrincipals=0; filter=; includedServicePrincipals=; excludedServicePrincipals=}
            isAllProtocolsEnabled  = $PolicyDetails.isAllProtocolsEnabled  # # : False
            isUsersGroupsV2Enabled = $PolicyDetails.isUsersGroupsV2Enabled # # : False
            isCloudAppsV2Enabled   = $PolicyDetails.isCloudAppsV2Enabled   # # : False
            version                = $PolicyDetails.version                # # : 1
            isFallbackUsed         = $PolicyDetails.isFallbackUsed         # # : False
        }
    }
}
function Get-O365AzureConditionalAccessTerms {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    # ?`$orderby=Name%20asc&`$filter=TypeId%20eq%208a76863a-a0e6-47a7-b99e-0410266eebcf
    # &x-tenantid=ceb371f6-8745-4876-a040-69f2d10a9d1a&{}&_=1631363067293
    $Uri = 'https://api.termsofuse.identitygovernance.azure.com/v1.1/Agreements'

    $QueryParameter = @{
        '$orderby'   = 'Name asc'
        '$filter'    = 'TypeId eq 8a76863a-a0e6-47a7-b99e-0410266eebcf'
        'x-tenantid' = $TenantID
    }

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Output
    }
}
function Get-O365AzureConditionalAccessVPN {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/Vpn/Certificates'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Output
    }
}
function Get-O365AzureEnterpriseAppsGroupConsent {
    # https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-user-consent?tabs=azure-portal
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://graph.microsoft.com/beta/settings'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            ($Output | Where-Object { $_.displayName -eq 'Consent Policy Settings' }).values
        } else {
            $ConsentPolicy = $Output | Where-Object { $_.displayName -eq 'Consent Policy Settings' }
            if ($ConsentPolicy) {
                $Object = [PSCustomObject] @{
                    EnableGroupSpecificConsent                      = ($ConsentPolicy.values | Where-Object { $_.name -eq 'EnableGroupSpecificConsent' } | Select-Object -ExpandProperty value)
                    BlockUserConsentForRiskyApps                    = $ConsentPolicy.values | Where-Object { $_.name -eq 'BlockUserConsentForRiskyApps' } | Select-Object -ExpandProperty value
                    EnableAdminConsentRequests                      = $ConsentPolicy.values | Where-Object { $_.name -eq 'EnableAdminConsentRequests' } | Select-Object -ExpandProperty value
                    ConstrainGroupSpecificConsentToMembersOfGroupId = $ConsentPolicy.values | Where-Object { $_.name -eq 'ConstrainGroupSpecificConsentToMembersOfGroupId' } | Select-Object -ExpandProperty value
                }
                if ($Object.EnableGroupSpecificConsent -eq 'true') {
                    $Object.EnableGroupSpecificConsent = $true
                } else {
                    $Object.EnableGroupSpecificConsent = $false
                }

                if ($Object.BlockUserConsentForRiskyApps -eq 'true') {
                    $Object.BlockUserConsentForRiskyApps = $true
                } else {
                    $Object.BlockUserConsentForRiskyApps = $false
                }
                if ($Object.EnableAdminConsentRequests -eq 'true') {
                    $Object.EnableAdminConsentRequests = $true
                } else {
                    $Object.EnableAdminConsentRequests = $false
                }
                $Object
            }
        }
    }
}
function Get-O365AzureEnterpriseAppsUserConsent {
    # https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-user-consent?tabs=azure-portal
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/UserSettings
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )

    $Uri = 'https://graph.microsoft.com/v1.0/policies/authorizationPolicy?$select=defaultUserRolePermissions'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            [PSCustomObject] @{
                allowedToCreateApps             = $Output.defaultUserRolePermissions.allowedToCreateApps
                allowedToCreateSecurityGroups   = $Output.defaultUserRolePermissions.allowedToCreateSecurityGroups
                allowedToReadOtherUsers         = $Output.defaultUserRolePermissions.allowedToReadOtherUsers
                permissionGrantPoliciesAssigned = Convert-AzureEnterpriseAppsUserConsent -PermissionsGrantPoliciesAssigned $Output.defaultUserRolePermissions.permissionGrantPoliciesAssigned
            }
        }
    }
}
function Get-O365AzureEnterpriseAppsUserSettings {
    # https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-user-consent?tabs=azure-portal
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/UserSettings
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/UserSettings/menuId/
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/EnterpriseApplications/UserSettings'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            [PSCustomObject] @{
                UsersCanConsentAppsAccessingData = $Output.usersCanAllowAppsToAccessData
                UsersCanAddGalleryAppsToMyApp    = $Output.usersCanAddGalleryApps
                UsersCanOnlySeeO365AppsInPortal  = $Output.hideOffice365Apps
            }
        }
    }
}
function Get-O365AzureEnterpriseAppsUserSettingsAdmin {
    # https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-user-consent?tabs=azure-portal
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/UserSettings
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/UserSettings/menuId/
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/RequestApprovals/V2/PolicyTemplates?type=AdminConsentFlow'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            [PSCustomObject] @{
                requestExpiresInDays = $Output.requestExpiresInDays
                notificationsEnabled = $Output.notificationsEnabled
                remindersEnabled     = $Output.remindersEnabled
                approvers            = $Output.approvers
                approversV2          = $Output.approversV2
            }
        }
    }
}
function Get-O365AzureEnterpriseAppsUserSettingsPromoted {
    # https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/configure-user-consent?tabs=azure-portal
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/UserSettings
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/StartboardApplicationsMenuBlade/UserSettings/menuId/
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/workspaces/promotedapps'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365AzureExternalCollaborationFlows {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    WARNING: Invoke-O365Admin - Error JSON: Response status code does not indicate success:
    403 (Forbidden). The application does not have any of the required delegated permissions
    (Policy.Read.All, Policy.ReadWrite.AuthenticationFlows) to access the resource.
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://graph.microsoft.com/v1.0/policies/authenticationFlowsPolicy'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365AzureExternalCollaborationSettings {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $ReverseGuestRole = @{
        'a0b1b346-4d3e-4e8b-98f8-753987be4970' = 'User'
        '10dae51f-b6af-4016-8d66-8c2a99b929b3' = 'GuestUser'
        '2af84b1e-32c8-42b7-82bc-daa82404023b' = 'RestrictedUser'
    }

    $Uri = 'https://graph.microsoft.com/v1.0/policies/authorizationPolicy'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            #id = $Output.id # : authorizationPolicy
            allowInvitesFrom                          = $Output.allowInvitesFrom                          # : adminsAndGuestInviters
            allowedToSignUpEmailBasedSubscriptions    = $Output.allowedToSignUpEmailBasedSubscriptions    # : True
            allowedToUseSSPR                          = $Output.allowedToUseSSPR                          # : True
            allowEmailVerifiedUsersToJoinOrganization = $Output.allowEmailVerifiedUsersToJoinOrganization # : False
            blockMsolPowerShell                       = $Output.blockMsolPowerShell                       # : False
            displayName                               = $Output.displayName                               # : Authorization Policy
            description                               = $Output.description                               # : Used to manage authorization related settings across the company.
            guestUserRoleId                           = $ReverseGuestRole[$Output.guestUserRoleId]                           # : a0b1b346-4d3e-4e8b-98f8-753987be4970
            defaultUserRolePermissions                = $Output.defaultUserRolePermissions                # :
        }
    }
}

<#
$o = Invoke-WebRequest -Uri "https://graph.microsoft.com/beta/policies/authenticationFlowsPolicy" -Headers @{
    "x-ms-client-session-id" = "a2f6c5f9b1b8450dbb0116f95ffbe9b2"
    "Accept-Language" = "en"
    "Authorization" = "Bearer .
    "x-ms-effective-locale" = "en.en-us"
    "Accept" = "*/*"
    #"Referer"=""
    "x-ms-client-request-id" = "d4bc027d-339c-46c2-ba96-c07f53fc5002"
    "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
}
$o.content
 
$p = Invoke-WebRequest -Uri "https://graph.microsoft.com/beta/policies/authorizationPolicy" -Headers @{
    "x-ms-client-session-id" = "a2f6c5f9b1b8450dbb0116f95ffbe9b2"
    "Accept-Language" = "en"
    "Authorization" = "Bearer ..
    "x-ms-effective-locale" = "en.en-us"
    "Accept" = "*/*"
    #"Referer" = ""
    "x-ms-client-request-id" = "d4bc027d-339c-46c2-ba96-c07f53fc5001"
    "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
}
$p.COntent
 
$g = Invoke-WebRequest -Uri "https://main.iam.ad.ext.azure.com/api/B2B/b2bPolicy" `
    -Headers @{
    "x-ms-client-session-id" = "02ca6867073543de9a89b767ad581135"
    "Accept-Language" = "en"
    "Authorization" = "Bearer "
    "x-ms-effective-locale" = "en.en-us"
    "Accept" = "*/*"
    #"Referer" = ""
    "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84"
    "x-ms-client-request-id" = "cf957d13-fc12-415d-a86a-1d74507d9003"
} `
    -ContentType "application/json"
$g.content
#>

function Get-O365AzureExternalIdentitiesEmail {
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/CompanyRelationshipsMenuBlade/IdentityProviders
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://graph.microsoft.com/beta/policies/authenticationmethodspolicy/authenticationMethodConfigurations/email'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            $Output
        }
    }
}

<# Requires
scp : AccessReview.ReadWrite.All AuditLog.Read.All Directory.AccessAsUser.All Directory.Read.All Directory.ReadWrite.All email EntitlementManagement.Read.All Group.ReadWrite.All IdentityProvider.ReadWrite.All IdentityRiskEvent.ReadWri
                      te.All IdentityUserFlow.Read.All openid Policy.Read.All Policy.ReadWrite.AuthenticationFlows Policy.ReadWrite.AuthenticationMethod Policy.ReadWrite.ConditionalAccess profile Reports.Read.All RoleManagement.ReadWrite.Directory Se
                      curityEvents.ReadWrite.All TrustFrameworkKeySet.Read.All User.Export.All User.ReadWrite.All UserAuthenticationMethod.ReadWrite.All
 
#>

function Get-O365AzureExternalIdentitiesPolicies {
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/CompanyRelationshipsMenuBlade/IdentityProviders
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/B2B/b2bPolicy'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            $Output
        }
    }
}
function Get-O365AzureFeatureConfiguration {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/FeatureConfigurations?supportAU=false'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Output
    }
}
function Get-O365AzureFeaturePortal {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://afd.hosting.portal.azure.net/iam/?bundlingKind=DefaultPartitioner&cacheability=3&clientOptimizations=true&environmentjson=true&extensionName=Microsoft_AAD_IAM&l=en&pageVersion=3.0.01692206&trustedAuthority=portal.azure.com'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Output
    }
}
function Get-O365AzureGroupExpiration {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/Directories/LcmSettings'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method Get
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            if ($Output.expiresAfterInDays -eq 0) {
                $GroupLifeTime = '180'
            } elseif ($Output.expiresAfterInDays -eq 1) {
                $GroupLifeTime = '365'
            } elseif ($Output.expiresAfterInDays -eq 2) {
                $GroupLifeTime = $Output.groupLifetimeCustomValueInDays
            }

            if ($Output.managedGroupTypes -eq 2) {
                $ExpirationEnabled = 'None'
            } elseif ($Output.managedGroupTypes -eq 1) {
                $ExpirationEnabled = 'Selected'
            } elseif ($Output.managedGroupTypes -eq 0) {
                $ExpirationEnabled = 'All'
            } else {
                $ExpirationEnabled = 'Unknown'
            }
            <#
            expiresAfterInDays : 2
            groupLifetimeCustomValueInDays : 185
            managedGroupTypesEnum : 0
            managedGroupTypes : 0
            adminNotificationEmails : przemyslaw.klys@evotec.pl
            groupIdsToMonitorExpirations : {}
            policyIdentifier : 6f843b54-8fa0-4837-a8e7-b01d00d25892
            #>


            [Array] $Groups = foreach ($ID in $Output.groupIdsToMonitorExpirations) {
                $Group = Get-O365Group -Id $ID
                if ($Group.id) {
                    $Group.DisplayName
                }
            }
            [PSCustomObject] @{
                GroupLifeTime           = $GroupLifeTime
                AdminNotificationEmails = $Output.adminNotificationEmails
                ExpirationEnabled       = $ExpirationEnabled
                ExpirationGroups        = $Groups
            }
        }
    }
}

function Get-O365AzureGroupGeneral {
    <#
    .SYNOPSIS
    Get settings for Azure Groups General Tab
 
    .DESCRIPTION
    Get settings for Azure Groups General Tab
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365AzureGroupGeneral -Verbose
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/General
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $SGGM = Get-O365AzureGroupSelfService -Headers $Headers
    if ($SGGM) {
        $OutputInformation = [ordered] @{}
        # weird thing is that changing this userDelegationEnabled which is not available on SGGM changes selfServiceGroupManagementEnabled
        $OutputInformation['OwnersCanManageGroupMembershipRequests'] = $SGGM.selfServiceGroupManagementEnabled
        $OutputInformation['SelfServiceRestrictUserAbilityAccessGroups'] = -not $SGGM.groupsInAccessPanelEnabled
        $OutputInformation['AllowedToCreateSecurityGroups'] = (Get-O365AzureGroupSecurity -Headers $Headers).AllowedToCreateSecurityGroups
        $OutputInformation['AllowedToCreateM365Groups'] = (Get-O365AzureGroupM365 -Headers $Headers).EnableGroupCreation
        [PSCustomObject] $OutputInformation
    }
}
function Get-O365AzureGroupM365 {
    <#
    .SYNOPSIS
    Get settings for Microsoft 365 Groups - "Users can create Microsoft 365 groups in Azure portals, API or PowerShell"
 
    .DESCRIPTION
    Get settings for Microsoft 365 Groups - "Users can create Microsoft 365 groups in Azure portals, API or PowerShell"
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER NoTranslation
    Provides output without any translation. Mostly required for testing or during internal configuration.
 
    .EXAMPLE
    Get-O365AzureGroupM365 -Verbose
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/General
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://graph.microsoft.com/beta/settings'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method Get
    if ($Output) {
        if ($NoTranslation) {
            $Output | Where-Object { $_.DisplayName -eq "Group.Unified" }
        } else {
            $Values = ($Output | Where-Object { $_.DisplayName -eq "Group.Unified" }).values
            $OutputInformation = [ordered] @{}
            if ($Values.Count -gt 1) {
                foreach ($Value in $Values) {
                    $OutputInformation[$Value.name] = if ($Value.value -eq "true") { $true } elseif ( $Value.value -eq "false") { $false } else { $Value.value }
                }
            }
            [PSCustomObject] $OutputInformation
        }
    }
}
function Get-O365AzureGroupNamingPolicy {
    <#
    .SYNOPSIS
    Gets the Azure Group naming policy.
 
    .DESCRIPTION
    Gets the Azure Group naming policy.
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER NoTranslation
    Provides output without any translation. Mostly required for testing or during internal configuration.
 
    .EXAMPLE
    Get-O365AzureGroupNamingPolicy
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/NamingPolicy
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://graph.microsoft.com/beta/settings'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method Get
    if ($Output) {
        if ($NoTranslation) {
            $Output | Where-Object { $_.DisplayName -eq "Group.Unified" }
        } else {
            $Values = ($Output | Where-Object { $_.DisplayName -eq "Group.Unified" }).values
            if ($Values.Count -gt 1) {
                $Prefix = $Values | Where-Object { $_.Name -eq 'PrefixSuffixNamingRequirement' }
                if ($Prefix.value) {
                    $PrefixSplit = $Prefix.value.split('[GroupName]')
                    [PSCUstomObject] @{
                        Prefix           = $PrefixSplit[0]
                        Suffix           = $PrefixSplit[1]
                        PrefixSuffixFull = $Prefix.value
                    }
                }
            }
        }
    }
}
function Get-O365AzureGroupSecurity {
    <#
    .SYNOPSIS
    Get settings for Security Groups - "Users can create security groups in Azure portals, API or PowerShell"
 
    .DESCRIPTION
    Get settings for Security Groups - "Users can create security groups in Azure portals, API or PowerShell"
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER NoTranslation
    Provides output without any translation. Mostly required for testing or during internal configuration.
 
    .EXAMPLE
    Get-O365AzureGroupSecurity -Verbose
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/General
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method Get


    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            [PSCustomObject] @{
                "AllowedToCreateSecurityGroups" = $Output.defaultUserRolePermissions.allowedToCreateSecurityGroups
            }
        }
    }
}

function Get-O365AzureGroupSelfService {
    <#
    .SYNOPSIS
    Gets Azure Groups Self Service information.
 
    .DESCRIPTION
    Gets Azure Groups Self Service information.
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365AzureGroupSelfService
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/Directories/SsgmProperties/'
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method Get

    if ($Output) {
        $Output
    }
}

function Get-O365AzureLicenses {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER LicenseName
    Parameter description
 
    .PARAMETER ServicePlans
    Parameter description
 
    .PARAMETER LicenseSKUID
    Parameter description
 
    .EXAMPLE
    $Licenses = Get-O365AzureLicenses
    $Licenses | Format-Table
 
    .EXAMPLE
    $ServicePlans = Get-O365AzureLicenses -ServicePlans -LicenseName 'Enterprise Mobility + Security E5' -Verbose
    $ServicePlans | Format-Table
 
    .EXAMPLE
    $ServicePlans = Get-O365AzureLicenses -ServicePlans -LicenseSKUID 'EMSPREMIUM' -Verbose
    $ServicePlans | Format-Table
 
    .EXAMPLE
    $ServicePlans = Get-O365AzureLicenses -ServicePlans -LicenseSKUID 'evotecpoland:EMSPREMIUM' -Verbose
    $ServicePlans | Format-Table
 
    .EXAMPLE
    $ServicePlans = Get-O365AzureLicenses -ServicePlans -LicenseSKUID 'evotecpoland:EMSPREMIUM' -IncludeLicenseDetails -Verbose
    $ServicePlans | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string] $LicenseName,
        [switch] $ServicePlans,
        [switch] $ServicePlansComplete,
        [string] $LicenseSKUID,
        [switch] $IncludeLicenseDetails
    )

    # Maybe change it to https://docs.microsoft.com/en-us/graph/api/subscribedsku-list?view=graph-rest-1.0&tabs=http
    # Or maybe not because it doesn't contain exactly same data missing displayName from service plans
    # $Uri = "https://graph.microsoft.com/v1.0/subscribedSkus"

    $Uri ="https://main.iam.ad.ext.azure.com/api/AccountSkus"

    $QueryParameter = @{
        backfillTenants = $false
    }

    if (-not $Script:AzureLicensesList) {
        Write-Verbose -Message "Get-O365AzureLicenses - Querying for Licenses SKU"
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
        # We build a list of all the licenses, for caching purposes
        if ($Output) {
            $Script:AzureLicensesList = $Output
        }
    } else {
        Write-Verbose -Message "Get-O365AzureLicenses - Reusing cache for Licenses SKU"
        $Output = $Script:AzureLicensesList
    }

    # If license name or license id is provided we filter thjings out
    if ($LicenseName) {
        $Output = $Output | Where-Object { $_.Name -eq $LicenseName }
    } elseif ($LicenseSKUID) {
        $Output = $Output | Where-Object {
            $TempSplit = $_.AccountSkuId -split ':'
            $TempSplit[1] -eq $LicenseSKUID -or $_.AccountSkuId -eq $LicenseSKUID
        }
    }

    # we then based on ServicePlans request only display service plans
    if ($ServicePlans) {
        foreach ($O in $Output) {
            if ($IncludeLicenseDetails) {
                foreach ($Plan in $O.serviceStatuses.servicePlan) {
                    [PSCustomObject] @{
                        LicenseName        = $O.Name
                        LicenseSKUID       = $O.AccountSkuId
                        ServiceDisplayName = $Plan.displayName
                        ServiceName        = $Plan.serviceName
                        ServicePlanId      = $Plan.servicePlanId
                        ServiceType        = $Plan.serviceType
                    }
                }
            } else {
                $O.serviceStatuses.servicePlan
            }
        }
    } elseif ($ServicePlansComplete) {
        # or display everything
        foreach ($O in $Output) {
            [PSCustomObject] @{
                Name           = $O.Name
                AccountSkuID   = $O.AccountSkuId
                ServicePlan    = $O.serviceStatuses.servicePlan
                AvailableUnits = $o.availableUnits
                TotalUnits     = $O.totalUnits
                ConsumedUnits  = $O.consumedUnits
                WarningUnits   = $O.warningUnits
            }
        }
    } else {
        $Output
    }
}


# https://main.iam.ad.ext.azure.com/api/AccountSkus/UserAssignments?accountSkuID=evotecpoland%3AEMSPREMIUM&nextLink=&searchText=&columnName=&sortOrder=undefined
# https://main.iam.ad.ext.azure.com/api/AccountSkus/UserAssignments?accountSkuID=evotecpoland%3AEMSPREMIUM&nextLink=&searchText=&columnName=&sortOrder=undefined
# https://main.iam.ad.ext.azure.com/api/AccountSkus/GroupAssignments?accountSkuID=evotecpoland%3AEMSPREMIUM&nextLink=&searchText=&sortOrder=undefined
function Get-O365AzureMultiFactorAuthentication {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365AzureMultiFactorAuthentication -Verbose
 
    .NOTES
    Based on: https://portal.azure.com/#blade/Microsoft_AAD_IAM/MultifactorAuthenticationMenuBlade/GettingStarted/fromProviders/
 
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    #$Uri = "https://main.iam.ad.ext.azure.com/api/MultiFactorAuthentication/GetOrCreateExpandedTenantModel?tenantName=Evotec"
    $Uri ="https://main.iam.ad.ext.azure.com/api/MultiFactorAuthentication/GetOrCreateExpandedTenantModel"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365AzureProperties {
    <#
    .SYNOPSIS
    Reads the properties of Azure Active Directory (Azure AD) for the current tenant.
 
    .DESCRIPTION
    Reads the properties of Azure Active Directory (Azure AD) for the current tenant.
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER NoTranslation
    Return information as provided by API, rather than translated to only values visible in GUI
 
    .EXAMPLE
    Get-O365AzureProperties -Verbose
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/Directory'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            [PSCustomObject] @{
                DisplayName          = $Output.DisplayName
                NotificationLanguage = $Output.preferredLanguage
                TechnicalContact     = if ($Output.technicalNotificationMails.Count -gt 0) { $Output.technicalNotificationMails[0] } else { '' }
                GlobalPrivacyContact = $Output.privacyProfile.contactEmail
                PrivacyStatementURL  = $Output.privacyProfile.statementUrl
            }
        }
    }
}
function Get-O365AzurePropertiesSecurity {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .EXAMPLE
    Get-O365AzurePropertiesSecurity -Verbose
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/SecurityDefaults/GetSecurityDefaultStatus'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Output
    }
}
function Get-O365AzureTenantSKU {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://main.iam.ad.ext.azure.com/api/TenantSkuInfo'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Output
    }
}
function Get-O365AzureUserSettings {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/Properties"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method GET
    if ($Output) {
        [PSCustomObject] @{
            objectId                                  = $Output.objectId                                  #: ceb371f6 - 8745 - 4876-a040 - 69f2d10a9d1a
            displayName                               = $Output.displayName                               #: Evotec
            usersCanRegisterApps                      = $Output.usersCanRegisterApps                      #: True
            isAnyAccessPanelPreviewFeaturesAvailable  = $Output.isAnyAccessPanelPreviewFeaturesAvailable  #: False
            showMyGroupsFeature                       = $Output.showMyGroupsFeature                       #: False
            myGroupsFeatureValue                      = $Output.myGroupsFeatureValue                      #:
            myGroupsGroupId                           = $Output.myGroupsGroupId                           #:
            myGroupsGroupName                         = $Output.myGroupsGroupName                         #:
            showMyAppsFeature                         = $Output.showMyAppsFeature                         #: False
            myAppsFeatureValue                        = $Output.myAppsFeatureValue                        #:
            myAppsGroupId                             = $Output.myAppsGroupId                             #:
            myAppsGroupName                           = $Output.myAppsGroupName                           #:
            showUserActivityReportsFeature            = $Output.showUserActivityReportsFeature            #: False
            userActivityReportsFeatureValue           = $Output.userActivityReportsFeatureValue           #:
            userActivityReportsGroupId                = $Output.userActivityReportsGroupId                #:
            userActivityReportsGroupName              = $Output.userActivityReportsGroupName              #:
            showRegisteredAuthMethodFeature           = $Output.showRegisteredAuthMethodFeature           #: False
            registeredAuthMethodFeatureValue          = $Output.registeredAuthMethodFeatureValue          #:
            registeredAuthMethodGroupId               = $Output.registeredAuthMethodGroupId               #:
            registeredAuthMethodGroupName             = $Output.registeredAuthMethodGroupName             #:
            usersCanAddExternalUsers                  = $Output.usersCanAddExternalUsers                  #: False
            limitedAccessCanAddExternalUsers          = $Output.limitedAccessCanAddExternalUsers          #: False
            restrictDirectoryAccess                   = $Output.restrictDirectoryAccess                   #: False
            groupsInAccessPanelEnabled                = $Output.groupsInAccessPanelEnabled                #: False
            selfServiceGroupManagementEnabled         = $Output.selfServiceGroupManagementEnabled         #: True
            securityGroupsEnabled                     = $Output.securityGroupsEnabled                     #: False
            usersCanManageSecurityGroups              = $Output.usersCanManageSecurityGroups              #:
            office365GroupsEnabled                    = $Output.office365GroupsEnabled                    #: False
            usersCanManageOfficeGroups                = $Output.usersCanManageOfficeGroups                #:
            allUsersGroupEnabled                      = $Output.allUsersGroupEnabled                      #: False
            scopingGroupIdForManagingSecurityGroups   = $Output.scopingGroupIdForManagingSecurityGroups   #:
            scopingGroupIdForManagingOfficeGroups     = $Output.scopingGroupIdForManagingOfficeGroups     #:
            scopingGroupNameForManagingSecurityGroups = $Output.scopingGroupNameForManagingSecurityGroups #:
            scopingGroupNameForManagingOfficeGroups   = $Output.scopingGroupNameForManagingOfficeGroups   #:
            objectIdForAllUserGroup                   = $Output.objectIdForAllUserGroup                   #:
            allowInvitations                          = $Output.allowInvitations                          #: False
            isB2CTenant                               = $Output.isB2CTenant                               #: False
            restrictNonAdminUsers                     = $Output.restrictNonAdminUsers                     #: False
            toEnableLinkedInUsers                     = $Output.toEnableLinkedInUsers                     #: {}
            toDisableLinkedInUsers                    = $Output.toDisableLinkedInUsers                    #: {}
            # We try to make it the same as shown in Set-O365UserSettings
            linkedInAccountConnection                 = if ($Output.enableLinkedInAppFamily -eq 4) { $true } elseif ($Output.enableLinkedInAppFamily -eq 0) { $true } else { $false }
            linkedInSelectedGroupObjectId             = $Output.linkedInSelectedGroupObjectId             #: b6cdb9c3-d660 - 4558-bcfd - 82c14a986b56
            linkedInSelectedGroupDisplayName          = $Output.linkedInSelectedGroupDisplayName          #: All Users
        }
    }
}
function Get-O365BillingAccounts {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/fd/jarvisCM/my-org/profiles?type=organization"
    $Uri ="https://admin.microsoft.com/fd/commerceMgmt/billingaccount"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365BillingInvoices {
    <#
    .SYNOPSIS
    Gets all invoices from Office 365. If no StartDate and EndDate are specified last 6 months are used.
 
    .DESCRIPTION
    Gets all invoices from Office 365. If no StartDate and EndDate are specified last 6 months are used.
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER StartDate
    Provide StartDate for the invoices to be retrieved. If not specified, StartDate is set to 6 months ago.
 
    .PARAMETER EndDate
    Provide EndDate for the invoices to be retrieved. If not specified, EndDate is set to current date.
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [DateTime] $StartDate,
        [DateTime] $EndDate
    )
    if (-not $StartDate) {
        $StartDate = (Get-Date).AddMonths(-6)
    }
    if (-not $EndDate) {
        $EndDate = Get-Date
    }
    $StartDateText = $StartDate.ToString("yyyy-MM-dd")
    $EndDateText = $EndDate.ToString("yyyy-MM-dd")
    $Uri ="https://admin.microsoft.com/fd/commerceapi/my-org/legacyInvoices(startDate=$StartDateText,endDate=$EndDateText)"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
<# Not sure where I got this from
function Get-O365BillingLicenseAutoClaim {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    if ($Headers) {
        $TentantID = $Headers.Tenant
    } else {
        $TentantID = $Script:AuthorizationO365Cache.Tenant
    }
    $Uri = "https://admin.microsoft.com/fd/m365licensing/v1/tenants/$TentantID/licenseddevicesassets"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output.items
}
#>

function Get-O365BillingLicenseAutoClaim {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/fd/m365licensing/v1/policies/autoclaim"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365BillingLicenseRequests {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    if ($Headers) {
        $TentantID = $Headers.Tenant
    } else {
        $TentantID = $Script:AuthorizationO365Cache.Tenant
    }
    $Uri ="https://admin.microsoft.com/fd/m365licensing/v1/tenants/$TentantID/self-service-requests"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output.items
}
function Get-O365BillingNotifications {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/fd/commerceMgmt/mgmtsettings/invoicePreference?api-version=1.0 "
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}

function Get-O365BillingNotificationsList {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/fd/commerceMgmt/mgmtsettings/billingNotificationUsers?api-version=1.0"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}

<# Not working
function Get-O365BillingNotificationsList {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
 
    $Uri = "https://admin.microsoft.com/admin/api/Users/ListBillingNotificationsUsers"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST
    $Output
}
 
Get-O365BillingNotificationsList
#>


function Get-O365BillingPaymentMethods {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string] $ID
    )

    if ($ID) {
        # $Uri = "https://admin.microsoft.com/fd/commerceapi/my-org/paymentInstruments($ID)/unsettledCharges"
        $Uri ="https://admin.microsoft.com/fd/commerceapi/my-org/paymentInstruments($ID)"
    } else {
        $Uri ="https://admin.microsoft.com/fd/commerceapi/my-org/paymentInstruments"
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365BillingProfile {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    # But how do I get AccountID ?
    $AccountID = ''
    $Uri ="https://admin.microsoft.com/fd/commerceMgmt/moderncommerce/myroles/BillingGroup?api-version=3.0&accountId=$AccountID"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method GET -Body $Body
    $Output
}
function Get-O365BillingSubscriptions {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string[]]$Property,
        [string] $OrderBy
    )

    #$Uri = "https://admin.microsoft.com/fd/commerceapi/my-org/subscriptions?`$filter=parentId%20eq%20null&`$expand=subscribedsku&optional=cspsubscriptions,price,actions,transitiondetails,quickstarttag"
    $Uri ="https://admin.microsoft.com/fd/commerceapi/my-org/subscriptions"

    $QueryParameter = @{
        '$Select'  = $Property -join ','
        '$filter'  = 'parentId eq null'
        '$orderby' = $OrderBy
        'expand'   = 'subscribedsku'
        'optional' = "cspsubscriptions,price,actions,transitiondetails,quickstarttag"
    }
    Remove-EmptyValue -Hashtable $QueryParameter

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
    $Output
}
function Get-O365ConsiergeAll {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/api/concierge/GetConciergeConfigAll"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365DirectorySync {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/dirsync"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365DirectorySyncErrors {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/dirsyncerrors/listdirsyncerrors"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST
    if ($Output.ObjectsWithErrorsList) {
        $Output.ObjectsWithErrorsList
    }
}
function Get-O365DirectorySyncManagement {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/DirsyncManagement/manage"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365Domain {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    #$Uri = "https://admin.microsoft.com/admin/api/Domains/List?filter=&searchText=&computeDomainRegistrationData=true"
    $Uri ="https://admin.microsoft.com/admin/api/Domains/List"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365DomainDependencies {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][string] $DomainName,
        [string][ValidateSet('All', 'Users', 'TeamsAndGroups', 'Apps')] $Type = 'All'
    )
    $Uri ="https://admin.microsoft.com/admin/api/Domains/Dependencies"

    $Types = @{
        'All'    = 0
        'Users'  = 1
        'Groups' = 2
        'Apps'   = 4
    }

    $QueryParameter = @{
        'domainName' = $DomainName
        'kind'       = $Types[$Type]
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter -Method POST
    if ($Output.Succeeded) {
        $Output.Data.Dependencies
    } else {
        [PSCustomObject] @{
            DomainName = $DomainName
            Status     = $false
            Message    = $Output.Message
        }
    }
}
function Get-O365DomainHealth {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][string] $DomainName
    )
    $Uri ="https://admin.microsoft.com/admin/api/Domains/CheckDnsHealth"

    $QueryParameter = @{
        'domainName'             = $DomainName
        'overrideSkip'           = $true
        'canRefreshCache'        = $true
        'dnsHealthCheckScenario' = 2
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
    if ($Output.Succeeded) {
        $Output.Data
    } else {
        $Output
    }
}

<#
function Get-O365DomainRegistrarsInformation {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers #,
       # [parameter(Mandatory)][string] $DomainName
    )
    $Uri = "https://admin.microsoft.com/admin/api/Domains/GetRegistrarsHelpInfo"
 
    $QueryParameter = @{
        #'domainName' = $DomainName
        #'overrideSkip' = $true
        #'canRefreshCache' = $true
        #'dnsHealthCheckScenario' = 2
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
    $Output
}
#>


function Get-O365DomainRecords {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][string] $DomainName
    )
    $Uri ="https://admin.microsoft.com/admin/api/Domains/Records"

    $QueryParameter = @{
        'domainName' = $DomainName
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
    $Output
}
function Get-O365DomainTroubleshooting {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][string] $DomainName
    )
    $Uri ="https://admin.microsoft.com/admin/api/Domains/CheckIsTroubleshootingAllowed"

    $QueryParameter = @{
        'domainName'      = $DomainName
        #'overrideSkip' = $true
        'canRefreshCache' = $true
        #'dnsHealthCheckScenario' = 2
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
    $Output
}

function Get-O365Group {
    [cmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [parameter(ParameterSetName = 'UnifiedGroupsOnly')]
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'EmailAddress')]
        [parameter(ParameterSetName = 'DisplayName')]
        [parameter(ParameterSetName = 'Id')]
        [alias('Authorization')][System.Collections.IDictionary] $Headers,

        [parameter(ParameterSetName = 'Id')][string] $Id,

        [parameter(ParameterSetName = 'DisplayName')][string] $DisplayName,

        [alias('Mail')][parameter(ParameterSetName = 'EmailAddress')][string] $EmailAddress,

        [parameter(ParameterSetName = 'UnifiedGroupsOnly')]
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'EmailAddress')]
        [parameter(ParameterSetName = 'DisplayName')]
        [parameter(ParameterSetName = 'Id')]
        [string[]] $Property,

        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')][string] $Filter,

        [parameter(ParameterSetName = 'UnifiedGroupsOnly')]
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [string] $OrderBy,

        [parameter(ParameterSetName = 'UnifiedGroupsOnly')]
        [switch] $UnifiedGroupsOnly
    )
    if ($DisplayName) {
        $Uri = 'https://graph.microsoft.com/v1.0/groups'
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$filter' = "displayName eq '$DisplayName'"
        }
    } elseif ($EmailAddress) {
        $Uri = 'https://graph.microsoft.com/v1.0/groups'
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$filter' = "mail eq '$EmailAddress'"
        }
    } elseif ($ID) {
        # Query a single group
        $Uri = "https://graph.microsoft.com/v1.0/groups/$ID"
        $QueryParameter = @{
            '$Select' = $Property -join ','
        }
    } elseif ($UnifiedGroupsOnly) {
        $Uri = "https://graph.microsoft.com/v1.0/groups"
        $QueryParameter = @{
            '$Select'  = $Property -join ','
            '$filter'  = "groupTypes/any(c: c eq 'Unified')"
            '$orderby' = $OrderBy
        }
    } else {
        # Query multiple groups
        $Uri = 'https://graph.microsoft.com/v1.0/groups'
        $QueryParameter = @{
            '$Select'  = $Property -join ','
            '$filter'  = $Filter
            '$orderby' = $OrderBy
        }
    }
    Remove-EmptyValue -Hashtable $QueryParameter
    Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
}
function Get-O365GroupAdministrativeUnit {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter()][string] $GroupID = '75233998-a950-41de-97d0-6c259d0580a7'
    )

    if ($GroupID) {
        $Group = $GroupID
    } elseif ($GroupDisplayName) {
        $Group = $GroupDisplayName
    }
    #$Uri = "https://graph.microsoft.com/beta/groups/$Group/memberOf/microsoft.graph.administrativeUnit"
    $Uri ="https://graph.microsoft.com/v1.0/groups/$Group/memberOf/microsoft.graph.administrativeUnit"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365GroupLicenses {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter()][string] $GroupID,
        [parameter()][alias('GroupName')][string] $GroupDisplayName,
        [switch] $ServicePlans,
        [switch] $NoTranslation
    )

    if ($GroupID) {
        $Group = $GroupID
    } elseif ($GroupDisplayName) {
        $GroupSearch = Get-O365Group -DisplayName $GroupDisplayName
        if ($GroupSearch.id) {
            $Group = $GroupSearch.id
        }
    }
    if ($Group) {
        $Uri ="https://main.iam.ad.ext.azure.com/api/AccountSkus/Group/$Group"
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
        if ($Output) {
            if ($NoTranslation) {
                $Output
            } else {
                foreach ($License in $Output.licenses) {
                    $SP = Convert-SKUToLicense -SKU $License.accountSkuID
                    if ($SP) {
                        $ServicePlansPrepared = Find-EnabledServicePlan -ServicePlans $SP -DisabledServicePlans $License.disabledServicePlans
                        [PSCustomObject] @{
                            License      = $SP[0].LicenseName
                            LicenseSKUID = $SP[0].LicenseSKUID
                            Enabled      = $ServicePlansPrepared.Enabled.ServiceDisplayName
                            Disabled     = $ServicePlansPrepared.Disabled.ServiceDisplayName
                            EnabledPlan  = $ServicePlansPrepared.Enabled
                            DisabledPlan = $ServicePlansPrepared.Disabled
                        }
                    }
                }
            }
        }
    }
}
function Get-O365GroupMember {
    [cmdletBinding()]
    param(
        [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][string] $Id,
        [string] $Search,
        [string[]] $Property
    )
    if ($ID) {
        # Query a single group
        $Uri = "https://graph.microsoft.com/v1.0/groups/$ID/members"
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$Search' = $Search

        }
        if ($QueryParameter.'$Search') {
            # This is required for search to work
            # https://developer.microsoft.com/en-us/identity/blogs/build-advanced-queries-with-count-filter-search-and-orderby/
            $Headers['ConsistencyLevel'] = 'eventual'
        }

        Remove-EmptyValue -Hashtable $QueryParameter
        Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
    }
}
function Get-O365OrgAzureSpeechServices {
    [cmdletbinding()]
    param(
        [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/azurespeechservices"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers

    [PSCustomobject] @{
        AllowTheOrganizationWideLanguageModel = $Output.IsTenantEnabled
    }
}
function Get-O365OrgBingDataCollection {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/settings/security/bingdatacollection"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgBookings {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/bookings"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgBriefingEmail {
    <#
    .SYNOPSIS
    Gets status of Briefing emails.
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/services/apps/briefingemail"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            IsMailEnabled         = $Output.IsMailEnabled
            IsSubscribedByDefault = $Output.IsSubscribedByDefault
        }
    }
}
function Get-O365OrgCalendarSharing {
    <#
    .SYNOPSIS
    Let your users share their calendars with people outside of your organization who have Office 365 or Exchange
 
    .DESCRIPTION
    Let your users share their calendars with people outside of your organization who have Office 365 or Exchange
 
    .PARAMETER Headers
    Authentication Token along with additional information that is created with Connect-O365Admin. If heaaders are not provided it will use the default token.
 
    .EXAMPLE
    Get-O365CalendarSharing
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/calendarsharing'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgCommunicationToUsers {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/EndUserCommunications'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgCortana {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://admin.microsoft.com/admin/api/services/apps/cortana'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgCustomerLockbox {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/security/dataaccess"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgCustomThemes {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/Settings/company/theme/v2"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output.ThemeData
}
function Get-O365OrgDataLocation {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/tenant/datalocation"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgDynamics365ConnectionGraph {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/dcg'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgDynamics365CustomerVoice {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/officeformspro'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        if ($NoTranslation) {
            $Output
        } else {
            [PSCustomObject] @{
                # Distribution section
                ReduceSurveyFatigueEnabled     = $Output.OverSurveyManagementEnabled    # : False
                ReduceSurveyFatigueDays        = $Output.OverSurveyManagementDays       # : 0
                CustomDomainEmails             = $Output.CustomDomainEmails             # : {}
                # Security section
                PreventPhishingAttemptsEnabled = $Output.InOrgFormsPhishingScanEnabled  # : True
                CollectNamesEnabled            = $Output.RecordIdentityByDefaultEnabled # : True
                RestrictSurveyAccessEnabled    = $Output.RestrictSurveyAccessEnabled    # : False

                # # not sure
                InOrgSurveyIncentiveEnabled    = $Output.InOrgSurveyIncentiveEnabled    # : False
                RequestType                    = $Output.RequestType                    # : 0
                ValidateDomain                 = $Output.ValidateDomain                 # :
            }
        }
    }
}
function Get-O365OrgDynamics365SalesInsights {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/dci'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgForms {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/officeforms/'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgGraphDataConnect {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/o365dataplan"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgHelpdeskInformation {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/Settings/company/helpdesk"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgInstallationOptions {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Branches = @{
        "0" = 'Not applicable'
        "1" = "CurrentChannel"
        "3" = 'MonthlyEnterpriseChannel'
        "2" = 'SemiAnnualEnterpriseChannel'
    }

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/usersoftware"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output.UserSoftwareSettings
    } else {
        if ($Output.UserSoftwareSettings) {
            [PSCustomObject] @{
                WindowsBranch           = $Branches[$($Output.UserSoftwareSettings[0].Branch.ToString())]
                WindowsClient           = $Output.UserSoftwareSettings[0].ClientVersion
                WindowsLastUpdate       = $Output.UserSoftwareSettings[0].BranchLastUpdateTime
                WindowsOffice           = $Output.UserSoftwareSettings[0].ServiceStatusMap.'Office (includes Skype for Business),MicrosoftOffice_ClientDownload'
                WindowsSkypeForBusiness = $Output.UserSoftwareSettings[0].ServiceStatusMap.'Skype for Business (Standalone),MicrosoftCommunicationsOnline';
                MacBranch               = $Branches[$($Output.UserSoftwareSettings[1].Branch.ToString())]
                MacClient               = $Output.UserSoftwareSettings[1].ClientVersion
                MacLastUpdate           = $Output.UserSoftwareSettings[1].BranchLastUpdateTime
                MacOffice               = $Output.UserSoftwareSettings[1].ServiceStatusMap.'Office,MicrosoftOffice_ClientDownload'
                MacSkypeForBusiness     = $Output.UserSoftwareSettings[1].LegacyServiceStatusMap.'Skype for Business (X EI Capitan 10.11 or higher),MicrosoftCommunicationsOnline'
            }
        }
    }
}
function Get-O365OrgM365Groups {
    <#
    .SYNOPSIS
    Choose how guests from outside your organization can collaborate with your users in Microsoft 365 Groups. Learn more about guest access to Microsoft 365 Groups
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    #$Uri = "https://admin.microsoft.com/admin/api/settings/security/guestUserPolicy"
    #$Output1 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    $Uri ="https://admin.microsoft.com/admin/api/settings/security/o365guestuser"
    $Output2 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    [PSCustomObject] @{
        #AllowGuestAccess = $Output1.AllowGuestAccess
        #AllowGuestInvitations = $Output1.AllowGuestInvitations
        #SitesSharingEnabled = $Output1.SitesSharingEnabled
        AllowGuestsAsMembers = $Output2.AllowGuestsAsMembers
        AllowGuestAccess     = $Output2.AllowGuestAccess
    }
}
function Get-O365OrgMicrosoftTeams {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/skypeteams"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
    <#
    IsSkypeTeamsLicensed : True
    TenantCategorySettings : {@{TenantSkuCategory=BusinessEnterprise; IsSkypeTeamsEnabled=; Meetups=; FunControl=; Messaging=}, @{TenantSkuCategory=Guest; IsSkypeTeamsEnabled=; Meetups=; FunControl=; Messaging=}}
    Bots : @{IsBotsEnabled=; IsSideLoadedBotsEnabled=; BotSettings=System.Object[]; IsExternalAppsEnabledByDefault=}
    Miscellaneous : @{IsOrganizationTabEnabled=; IsSkypeBusinessInteropEnabled=; IsTBotProactiveMessagingEnabled=}
    Email : @{IsEmailIntoChannelsEnabled=; RestrictedSenderList=System.Object[]}
    CloudStorage : @{Box=; Dropbox=; GoogleDrive=; ShareFile=}
    TeamsOwnedApps : @{TeamsOwnedAppSettings=System.Object[]}
    TenantOwnedApps : @{TenantOwnedAppSettings=System.Object[]}
    MigrationStates : @{EnableAppsMigration=; EnableClientSettingsMigration=; EnableMeetupsMigration=; EnableMessagingMigration=}
    #>

}
function Get-O365OrgModernAuthentication {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .EXAMPLE
    Get-O365OrgModernAuthentication -Verbose
 
    .NOTES
    https://admin.microsoft.com/#/Settings/Services/:/Settings/L1/ModernAuthentication
    #>

    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/modernAuth"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgMyAnalytics {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/myanalytics"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            EnableInsightsDashboard    = -not $Output.IsDashboardOptedOut
            EnableWeeklyDigest         = -not $Output.IsEmailOptedOut
            EnableInsightsOutlookAddIn = -not $Output.IsAddInOptedOut
            # IsNudgesOptedOut : False
            # IsWindowsSignalOptedOut : False
            # MeetingEffectivenessSurvey : Unavailable
        }
    }
}
function Get-O365OrgNews {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )
    $Uri ="https://admin.microsoft.com/admin/api/searchadminapi/news/options/Bing"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output
    } else {
        If ($Output) {
            [PSCustomObject] @{
                ContentOnNewTabEnabled               = $Output.NewsOptions.EdgeNTPOptions.IsOfficeContentEnabled
                CompanyInformationAndIndustryEnabled = $Output.NewsOptions.EdgeNTPOptions.IsShowCompanyAndIndustry
            }
        }
    }
}
function Get-O365OrgOfficeOnTheWeb {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/officeonline"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgOfficeProductivity {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/reports/productivityScoreCustomerOption"
    $Output1 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    #$Uri = "https://admin.microsoft.com/admin/api/reports/productivityScoreConfig/GetProductivityScoreConfig"
    #$Output2 = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output1) {
        #$Output2Json = $Output2.Output | ConvertFrom-Json
        $Output1Json = $Output1.Output | ConvertFrom-Json
        $Output = [PSCustomObject] @{
            #TenantId = $Output2Json.TenantId
            #ProductivityScoreSignedup = $Output2Json.ProductivityScoreSignedup
            #SignupUserPuid = $Output2Json.SignupUserPuid
            #SignupTime = $Output2Json.SignupTime
            #ReadyTime = $Output2Json.ReadyTime
            ProductivityScoreOptedIn = $Output1Json.ProductivityScoreOptedIn
            OperationUserPuid        = $Output1Json.OperationUserPuid
            OperationTime            = $Output1Json.OperationTime
        }
        $Output
    }
}
function Get-O365OrgOrganizationInformation {
    [alias('Get-O365OrgCompanyInformation')]
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )

    $Uri ="https://admin.microsoft.com/admin/api/Settings/company/profile"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output
    } else {
        $Output
    }
}
function Get-O365OrgPasswordExpirationPolicy {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )

    $Uri ="https://admin.microsoft.com/admin/api/Settings/security/passwordpolicy"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output
    } else {
        [PSCustomObject] @{
            PasswordNeverExpires      = $Output.NeverExpire
            DaysBeforePasswordExpires = $Output.ValidityPeriod
            DaysBeforeUserNotified    = $Output.NotificationDays
            # not shown in the GUI
            # MinimumValidityPeriod : 14
            # MinimumNotificationDays : 1
            # MaximumValidityPeriod : 730
            # MaximumNotificationDays : 30
        }
    }
}
function Get-O365OrgPlanner {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/services/apps/planner"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            # Thos are always the same
            #id = $Output.id # : 1
            #isPlannerAllowed = $Output.isPlannerAllowed # : True
            allowCalendarSharing = $Output.allowCalendarSharing # : True
            # GUI doesn't show that
            # allowTenantMoveWithDataLoss = $Output.allowTenantMoveWithDataLoss # : False
            # allowRosterCreation = $Output.allowRosterCreation # : True
            # allowPlannerMobilePushNotifications = $Output.allowPlannerMobilePushNotifications # : True
        }
    }
}
function Get-O365OrgPrivacyProfile {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/Settings/security/privacypolicy"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgPrivilegedAccess {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/Settings/security/tenantLockbox"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgProject {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/projectonline"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output
    } else {
        if ($Output) {
            [PSCustomObject] @{
                RoadmapEnabled          = $Output.IsRoadmapEnabled
                ProjectForTheWebEnabled = $Output.IsModProjEnabled
            }
        }
    }
}
function Get-O365OrgReports {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/reports/config/GetTenantConfiguration"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $OutputFromJson = $Output.Output | ConvertFrom-Json
    $OutputFromJson
}
function Get-O365OrgScripts {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Status = @{
        '0' = 'Disabled'
        '1' = 'Everyone'
        '2' = 'SpecificGroup'
    }
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/officescripts"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            # we don't show those options as they have no values
            # we also don't show them as there is no option in GUI
            #OfficeScriptsEnabled = $Output.OfficeScriptsEnabled # :
            #OfficeScriptsPreviewEnabled = $Output.OfficeScriptsPreviewEnabled # :
            EnabledOption         = $Status[$($Output.EnabledOption).ToString()]               # : 1
            EnabledGroup          = $Output.EnabledGroup                # :
            EnabledGroupDetail    = $Output.EnabledGroupDetail          # :
            ShareOption           = $Status[$($Output.ShareOption).ToString()]                 # : 1
            ShareGroup            = $Output.ShareGroup                  # :
            ShareGroupDetail      = $Output.ShareGroupDetail            # :
            UnattendedOption      = $Status[$($Output.UnattendedOption).ToString()]            # : 0
            UnattendedGroup       = $Output.UnattendedGroup             # :
            UnattendedGroupDetail = $Output.UnattendedGroupDetail       # :
            #TenantId = $Output.TenantId # :
        }
    }
}
function Get-O365OrgSharePoint {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $TranslateCollaboration = @{
        '2'  = 'NewAndExistingGuestsOnly'
        '16' = 'Anyone'
        '32' = 'ExistingGuestsOnly'
        '1'  = 'OnlyPeopleInYourOrganization'
    }
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/sitessharing"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            AllowSharing                      = $Output.AllowSharing
            SiteUrl                           = $Output.SiteUrl
            AdminUrl                          = $Output.AdminUrl
            RequireAnonymousLinksExpireInDays = $Output.RequireAnonymousLinksExpireInDays
            CollaborationType                 = $TranslateCollaboration[$Output.CollaborationType.ToString()]
        }
    }
}
function Get-O365OrgSharing {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )

    $Uri ="https://admin.microsoft.com/admin/api/settings/security/guestUserPolicy"
    $Output1 = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output1
    } else {
        # In fiddler we coudld see additional queries, but in edge/chrome not so much
        #$Uri = "https://admin.microsoft.com/admin/api/settings/apps/sitessharing"
        #$Output2 = Invoke-O365Admin -Uri $Uri -Headers $Headers
        #$Output2 | Format-Table

        # $Uri = "https://admin.microsoft.co//admin/api/settings/security/o365guestuser"
        # $Output3 = Invoke-O365Admin -Uri $Uri -Headers $Headers
        # $Output3 | Format-Table
        if ($Output1) {
            [PSCustomObject] @{
                # GUI doesn't show them, so mayne lets not show them eiter
                #AllowGuestAccess = $Output1.AllowGuestAccess
                LetUsersAddNewGuests = $Output1.AllowGuestInvitations
                #SitesSharingEnabled = $Output1.SitesSharingEnabled
                #AllowSharing = $Output2.AllowSharing
                #SiteUrl = $Output2.SiteUrl
                #AdminUri = $Output2.AdminUri
                #RequireAnonymousLinksExpireInDays = $Output2.RequireAnonymousLinksExpireInDays
                #CollaborationType = $Output2.CollaborationType
            }
        }
    }
}
function Get-O365OrgSway {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/Sway"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgToDo {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/todo"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365OrgUserConsentApps {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/IntegratedApps"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($null -ne $Output) {
        [PSCustomObject] @{
            UserConsentToAppsEnabled = $Output
        }
    }
}
function Get-O365OrgUserOwnedApps {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/store"
    $Output1 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    $Uri ="https://admin.microsoft.com/admin/api/storesettings/iwpurchaseallowed"
    $Output2 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    $Uri = 'https://admin.microsoft.com/fd/m365licensing/v1/policies/autoclaim'
    $Output4 = Invoke-O365Admin -Uri $Uri -Headers $Headers

    [PSCustomObject] @{
        LetUsersAccessOfficeStore = $Output1
        LetUsersStartTrials       = $Output2
        LetUsersAutoClaimLicenses = if ($Output4.tenantPolicyValue -eq 'Disabled') { $false } elseif ($Output4.tenantPolicyValue -eq 'Enabled') { $true } else { $null }
        <#
        {
        "policyId": "Autoclaim",
        "tenantPolicyValue": "Enabled",
        "tenantId": "ceb371f6-"
        }
        #>


    }
}
function Get-O365OrgWhiteboard {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [switch] $NoTranslation
    )

    $TranslateTelemetry = @{
        '0' = 'Neither'
        '1' = 'Required'
        '2' = 'Optional'
    }
    $Uri = 'https://admin.microsoft.com/admin/api/settings/apps/whiteboard'

    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($NoTranslation) {
        $Output
    } else {
        if ($Output) {
            [PSCustomObject] @{
                WhiteboardEnabled            = $Output.IsEnabled
                DiagnosticData               = $TranslateTelemetry[$Output.TelemetryPolicy.ToString()]
                OptionalConnectedExperiences = $Output.AreConnectedServicesEnabled
                BoardSharingEnabled          = $Output.IsClaimEnabled
                OneDriveStorageEnabled       = $Output.IsSharePointDefault
                # Not sure what this does
                NonTenantAccess              = $Output.NonTenantAccess
                #LearnMoreUrl = $Output.LearnMoreUrl
                #ProductUrl = $Output.ProductUrl
                #TermsOfUseUrl = $Output.TermsOfUseUrl
            }
        }
    }
}
function Get-O365PartnerRelationship {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string] $TenantID
    )
    if (-not $TenantID) {
        if ($Headers.Tenant) {
            $TenantID = $Headers.Tenant
        } elseif ($Script:AuthorizationO365Cache.Tenant) {
            $TenantID = $Script:AuthorizationO365Cache.Tenant
        }
    }
    if ($TenantID) {
        $Uri ="https://admin.microsoft.com/fd/commerceMgmt/partnermanage/partners?customerTenantId=$TenantID&api-version=2.1"
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers

        if ($Output.partners) {
            foreach ($Partner in $Output.partners) {
                [PSCustomObject] @{
                    id            = $Partner.id  #: c2248f0a
                    name          = $Partner.name  #:
                    aadRoles      = Convert-AzureRole -RoleID $Partner.aadRoles
                    # i am not 100% sure on the conversion types on different numbers so i'll disable them for now
                    companyType   = $Partner.companyType #Convert-CompanyType -CompanyType $Partner.companyType #: 4
                    canRemoveDap  = $Partner.canRemoveDap  #: True
                    contractTypes = $Partner.contractTypes # Convert-ContractType -ContractType $Partner.contractTypes #: {3}
                    partnerType   = $Partner.partnerType  #: 1
                }
            }
        }
    } else {
        Write-Warning -Message "Get-O365PartnerRelationship - TenantID was not found in headers. Skipping."
    }
}
function Get-O365PasswordReset {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/PasswordReset/PasswordResetPolicies"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365PasswordResetIntegration {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    #$Uri = "https://main.iam.ad.ext.azure.com/api/PasswordReset/IsOnPremisesPasswordResetAvailable"
    $Uri ="https://main.iam.ad.ext.azure.com/api/PasswordReset/OnPremisesPasswordResetPolicies"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            PasswordWritebackSupported = $Output.passwordWritebackSupported
            # This one doesn't change and stays enabled all the time
            #AccountUnlockSupported = $Output.accountUnlockSupported
            AccountUnlockEnabled       = $Output.accountUnlockEnabled
        }
    }
}
function Get-O365OrgReleasePreferences {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/Settings/company/releasetrack"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    $Output
}
function Get-O365SearchIntelligenceBingConfigurations {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/admin/api/searchadminapi/configurations"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers

    if ($Output) {
        [PSCustomObject] @{
            # GUI only allowes single change to all services at once - this means if one is TRUE else is TRUE
            ServiceEnabled = if ($Output.People -eq $true) { $true } else { $false }
            People         = $Output.People
            Groups         = $Output.Groups
            Documents      = $Output.Documents
            Yammer         = $Output.Yammer
            Teams          = $Output.Teams
            TenantState    = $Output.TenantState
        }
    }
}
function Get-O365SearchIntelligenceBingExtension {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )
    $Uri ="https://admin.microsoft.com/fd/bfb/api/v3/office/switch/feature"
    $OutputBing = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST
    if ($OutputBing) {
        [PSCustomObject] @{
            BingDefaultEngine = if ($OutputBing.result[0] -eq 'BingDefault') { $true } else { $false }
            #BingDefaultGroup = if ($OutputBing.result[1] -eq 'BingDefaultGroupWise') { $true } else { $false }
            BingDefaultGroups = if ($OutputBing.bingDefaultsEnabledGroups) { $OutputBing.bingDefaultsEnabledGroups } else { $null }
        }
    }
}
function Get-O365SearchIntelligenceItemInsights {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/fd/configgraphprivacy/ceb371f6-8745-4876-a040-69f2d10a9d1a/settings/ItemInsights"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        $Return = [PSCustomObject] @{
            AllowItemInsights  = $Output.isEnabledInOrganization
            DisabledForGroup   = $null
            DisabledForGroupID = $Output.disabledForGroup
        }
        if ($Output.DisabledForGroup) {
            $Group = Get-O365Group -Id $Output.DisabledForGroup -Headers $Headers
            if ($Group.id) {
                $Return.DisabledForGroup = $Group.displayName
            }
        }
        $Return
    }
}
function Get-O365SearchIntelligenceMeetingInsights {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers
    )

    $Uri ="https://admin.microsoft.com/fd/ssms/api/v1.0/'3srecs'/Collection('meetinginsights')/Settings(Path=':',LogicalId='MeetingInsightsToggle')"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers
    if ($Output) {
        [PSCustomObject] @{
            AllowMeetingInsights = $Output.Payload -eq 'true'
        }
    }
}
function Get-O365ServicePrincipal {
    [cmdletBinding(DefaultParameterSetName = "Default")]
    param(
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'ServicePrincipalType')]
        [parameter(ParameterSetName = 'AppDisplayName')]
        [parameter(ParameterSetName = 'Id')]
        [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers,

        [parameter(ParameterSetName = 'Id')][string] $Id,

        [parameter(ParameterSetName = 'AppDisplayName')][string] $DisplayName,

        [ValidateSet('Application', 'Legacy', 'SocialIdp')][parameter(ParameterSetName = 'servicePrincipalType')][string] $ServicePrincipalType,

        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'ServicePrincipalType')]
        [parameter(ParameterSetName = 'AppDisplayName')]
        [parameter(ParameterSetName = 'Id')]
        [string[]] $Property,

        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')][string] $Filter,

        [parameter(ParameterSetName = 'GuestsOnly')][switch] $GuestsOnly,

        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [string] $OrderBy
    )
    if ($GuestsOnly) {
        $Uri = 'https://graph.microsoft.com/v1.0/servicePrincipals'
        $QueryParameter = @{
            '$Select'  = $Property -join ','
            '$filter'  = "userType eq 'Guest'"
            '$orderby' = $OrderBy
        }
    } elseif ($DisplayName) {
        $Uri = 'https://graph.microsoft.com/v1.0/servicePrincipals'
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$filter' = "displayName eq '$DisplayName'"
        }
    } elseif ($ServicePrincipalType) {
        $Uri = 'https://graph.microsoft.com/v1.0/servicePrincipals'
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$filter' = "servicePrincipalType eq '$ServicePrincipalType'"
        }
    } elseif ($ID) {
        # Query a single group
        $Uri = "https://graph.microsoft.com/v1.0/servicePrincipals/$ID"
        $QueryParameter = @{
            '$Select' = $Property -join ','
        }
    } else {
        # Query multiple groups
        $Uri = 'https://graph.microsoft.com/v1.0/servicePrincipals'
        $QueryParameter = @{
            '$Select'  = $Property -join ','
            # https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
            '$filter'  = $Filter
            '$orderby' = $OrderBy
        }
    }
    Remove-EmptyValue -Hashtable $QueryParameter
    Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
}
function Get-O365TenantID {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Domain
    Parameter description
 
    .EXAMPLE
    Get-O365TenantID -Domain 'evotec.pl'
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [parameter(Mandatory)][alias('DomainName')][string] $Domain
    )
    $Invoke = Invoke-RestMethod"https://login.windows.net/$Domain/.well-known/openid-configuration"-MethodGET -Verbose:$false
    if ($Invoke) {
        $Invoke.userinfo_endpoint.Split("/")[3]
    }
}
function Get-O365User {
    [cmdletBinding(DefaultParameterSetName = "Default")]
    param(
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'EmailAddress')]
        [parameter(ParameterSetName = 'UserPrincipalName')]
        [parameter(ParameterSetName = 'Id')]
        [parameter()][alias('Authorization')][System.Collections.IDictionary] $Headers,

        [parameter(ParameterSetName = 'Id')][string] $Id,

        [parameter(ParameterSetName = 'UserPrincipalName')][string] $UserPrincipalName,

        [alias('Mail')][parameter(ParameterSetName = 'EmailAddress')][string] $EmailAddress,

        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'EmailAddress')]
        [parameter(ParameterSetName = 'UserPrincipalName')]
        [parameter(ParameterSetName = 'Id')]
        [string[]] $Property,

        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')][string] $Filter,

        [parameter(ParameterSetName = 'GuestsOnly')][switch] $GuestsOnly,

        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [string] $OrderBy,

        [parameter(ParameterSetName = 'Default')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'GuestsOnly')]
        [parameter(ParameterSetName = 'EmailAddress')]
        [parameter(ParameterSetName = 'UserPrincipalName')]
        [parameter(ParameterSetName = 'Id')]
        [switch] $IncludeManager
    )
    if ($GuestsOnly) {
        $Uri = 'https://graph.microsoft.com/v1.0/users'
        $QueryParameter = @{
            '$Select'  = $Property -join ','
            '$filter'  = "userType eq 'Guest'"
            '$orderby' = $OrderBy
        }
    } elseif ($UserPrincipalName) {
        $Uri = 'https://graph.microsoft.com/v1.0/users'
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$filter' = "userPrincipalName eq '$UserPrincipalName'"
        }
    } elseif ($EmailAddress) {
        $Uri = 'https://graph.microsoft.com/v1.0/users'
        $QueryParameter = @{
            '$Select' = $Property -join ','
            '$filter' = "mail eq '$EmailAddress'"
        }
    } elseif ($ID) {
        # Query a single group
        $Uri = "https://graph.microsoft.com/v1.0/users/$ID"
        $QueryParameter = @{
            '$Select' = $Property -join ','
        }
    } else {
        # Query multiple groups
        $Uri = 'https://graph.microsoft.com/v1.0/users'
        $QueryParameter = @{
            '$Select'  = $Property -join ','
            # https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter
            '$filter'  = $Filter
            '$orderby' = $OrderBy
        }
    }
    # https://docs.microsoft.com/en-us/graph/api/user-list-manager?view=graph-rest-1.0&tabs=http
    if ($IncludeManager) {
        $QueryParameter['$expand'] = 'manager'
    }
    Remove-EmptyValue -Hashtable $QueryParameter
    Invoke-O365Admin -Uri $Uri -Headers $Headers -QueryParameter $QueryParameter
}
function Invoke-O365Admin {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [uri] $Uri,
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [validateset('GET', 'DELETE', 'POST', 'PATCH', 'PUT')][string] $Method = 'GET',
        [string] $ContentType = "application/json; charset=UTF-8",
        [System.Collections.IDictionary] $Body,
        [System.Collections.IDictionary] $QueryParameter
    )
    if (-not $Headers -and $Script:AuthorizationO365Cache) {
        # This forces a reconnect of session in case it's about to time out. If it's not timeouting a cache value is used
        $Headers = Connect-O365Admin -Headers $Headers
    } elseif ($Headers) {
        $Headers = Connect-O365Admin -Headers $Headers
    } else {
        Write-Warning "Invoke-O365Admin - Not connected. Please connect using Connect-O365Admin."
        return
    }
    if (-not $Headers) {
        Write-Warning "Invoke-O365Admin - Authorization error. Skipping."
        return
    }
    $RestSplat = @{
        Method      = $Method
        ContentType = $ContentType
    }
    if ($Uri -like '*admin.microsoft.com*') {
        $RestSplat['Headers'] = $Headers.HeadersO365
    } elseif ($Uri -like '*graph.microsoft.com*') {
        $RestSplat['Headers'] = $Headers.HeadersGraph
    } else {
        $RestSplat['Headers'] = $Headers.HeadersAzure
    }

    if ($PSVersionTable.PSVersion.Major -eq 5) {
        $CookieContainer = [System.Net.CookieContainer]::new()
        $CookieContainer.MaxCookieSize = 1048576

        $Session = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
        $Session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.38"
        $Session.Cookies = $CookieContainer
        $RestSplat['WebSession'] = $Session
    }

    #$RestSplat.Headers."x-ms-mac-hosting-app" = 'M365AdminPortal'
    #$RestSplat.Headers."x-ms-mac-version" = 'host-mac_2021.8.16.1'
    #$RestSplat.Headers."sec-ch-ua" = '"Chromium";v="92", " Not A;Brand";v="99", "Microsoft Edge";v="92"'
    #$RestSplat.Headers."x-portal-routekey" = 'weu'
    #$RestSplat.Headers."x-ms-mac-appid" = 'feda2aab-4737-4646-a86c-98a7742c70e6'
    #$RestSplat.Headers."x-adminapp-request" = '/Settings/Services/:/Settings/L1/Whiteboard'
    #$RestSplat.Headers."x-ms-mac-target-app" = 'MAC'
    #$RestSplat.UserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.73'
    #$RestSplat.Headers.Cookie = 'MC1=GUID=480c128a5ba04faea7df151a53bdfa9a&HASH=480c&LV=202107&V=4&LU=1627670649689'

    #$RestSplat.Headers."x-ms-mac-hosting-app" = 'M365AdminPortal'
    #$RestSplat.Headers."x-adminapp-request" = '/Settings/Services/:/Settings/L1/EndUserCommunications'
    #$RestSplat.Headers."Referer" = 'https://admin.microsoft.com/'
    #$RestSplat.Headers."AjaxSessionKey" = 'x5eAwqzbVehBOP7QHfrjpwr9eYtLiHJt7TZFj0uhUMUPQ2T7yNdA7rEgOulejHDHYM1ZyCT0pgXo96EwrfVpMA=='
    #$RestSplat.Headers."etag" = '1629993527.826253_3ce8143d'

    if ($Body) {
        $RestSplat['Body'] = $Body | ConvertTo-Json -Depth 5
    }
    $RestSplat.Uri = Join-UriQuery -BaseUri $Uri -QueryParameter $QueryParameter
    if ($RestSplat['Body']) {
        $WhatIfInformation = "Invoking [$Method] " + [System.Environment]::NewLine + $RestSplat['Body'] + [System.Environment]::NewLine
    } else {
        $WhatIfInformation = "Invoking [$Method] "
    }
    try {
        Write-Verbose "Invoke-O365Admin - $($WhatIfInformation)over URI $($RestSplat.Uri)"
        if ($Method -eq 'GET') {
            # We use separate check because WHATIF would sometimes trigger when GET was used inside a SET
            $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false
            if ($null -ne $OutputQuery) {
                if ($OutputQuery -is [bool]) {
                    $OutputQuery
                } elseif ($OutputQuery -is [array]) {
                    $Properties = $OutputQuery | Select-Properties -ExcludeProperty '@odata.context', '@odata.id', '@odata.type', 'Length' -WarningAction SilentlyContinue -WarningVariable varWarning
                    if (-not $varWarning) {
                        $OutputQuery | Select-Object -Property $Properties
                    }
                } elseif ($OutputQuery -is [string]) {
                    if ($OutputQuery) {
                        $Properties = $OutputQuery | Select-Properties -ExcludeProperty '@odata.context', '@odata.id', '@odata.type', 'Length' -WarningAction SilentlyContinue -WarningVariable varWarning
                        if (-not $varWarning) {
                            $OutputQuery | Select-Object -Property $Properties
                        }
                    }
                } elseif ($OutputQuery -is [PSCustomObject]) {
                    if ($OutputQuery.PSObject.Properties.Name -contains 'value') {
                        $Properties = $OutputQuery.value | Select-Properties -ExcludeProperty '@odata.context', '@odata.id', '@odata.type', 'Length' -WarningAction SilentlyContinue -WarningVariable varWarning
                        if (-not $varWarning) {
                            $OutputQuery.value | Select-Object -Property $Properties
                        }
                    } else {
                        $Properties = $OutputQuery | Select-Properties -ExcludeProperty '@odata.context', '@odata.id', '@odata.type', 'Length' -WarningAction SilentlyContinue -WarningVariable varWarning
                        if (-not $varWarning) {
                            $OutputQuery | Select-Object -Property $Properties
                        }
                    }
                } else {
                    Write-Warning -Message "Invoke-O365Admin - Type $($OutputQuery.GetType().Name) potentially unsupported."
                    $OutputQuery
                }
            }
            if ($OutputQuery -isnot [array]) {
                if ($OutputQuery.'@odata.nextLink') {
                    $RestSplat.Uri = $OutputQuery.'@odata.nextLink'
                    if ($RestSplat.Uri) {
                        # We must remove websession parameter because Invoke-o365admin doesn't have it and i don't want to add it to the code
                        # it will set it self anyways
                        $RestSplat.Remove('WebSession')
                        # We need to reset the headers to full header, rather than the one that was used to invoke the previous call
                        $RestSplat.Headers = $Headers
                        # Not sure if this is best/fastest way to do it, but it works
                        # It's a bit better than saving it to variable and releasing everything later on as it can be used in pipeline
                        Invoke-O365Admin @RestSplat | ForEach-Object { if ($null -ne $_) { $_ } }
                        #if ($null -ne $MoreData) {
                        # $MoreData
                        #}
                    }
                }
            }
        } else {
            if ($PSCmdlet.ShouldProcess($($RestSplat.Uri), $WhatIfInformation)) {
                #$CookieContainer = [System.Net.CookieContainer]::new()
                #$CookieContainer.MaxCookieSize = 8096
                $OutputQuery = Invoke-RestMethod @RestSplat -Verbose:$false
                if ($Method -in 'POST', 'PUT') {
                    if ($null -ne $OutputQuery) {
                        $OutputQuery
                    }
                } else {
                    return $true
                }
            }
        }
    } catch {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            Write-Error $_
            return
        }
        $RestError = $_.ErrorDetails.Message
        $RestMessage = $_.Exception.Message
        if ($RestError) {
            try {
                $ErrorMessage = ConvertFrom-Json -InputObject $RestError -ErrorAction Stop
                $ErrorText = $ErrorMessage.error.message
                # Write-Warning -Message "Invoke-O365Admin - [$($ErrorMessage.error.code)] $($ErrorMessage.error.message), exception: $($_.Exception.Message)"
                Write-Warning -Message "Invoke-O365Admin - Error: $($RestMessage) $($ErrorText)"
            } catch {
                $ErrorText = ''
                Write-Warning -Message "Invoke-O365Admin - Error: $($RestMessage)"
            }
        } else {
            Write-Warning -Message "Invoke-O365Admin - Error: $($_.Exception.Message)"
        }
        if ($_.ErrorDetails.RecommendedAction) {
            Write-Warning -Message "Invoke-O365Admin - Recommended action: $RecommendedAction"
        }
        if ($Method -notin 'GET', 'POST') {
            return $false
        }
    }
}
function New-O365License {
    <#
    .SYNOPSIS
    Helper cmdlet to create a new O365 license that is used in Set-O365AzureGroupLicenses cmdlet.
 
    .DESCRIPTION
    Helper cmdlet to create a new O365 license that is used in Set-O365AzureGroupLicenses cmdlet.
 
    .PARAMETER LicenseName
    LicenseName to assign. Can be used instead of LicenseSKUID
 
    .PARAMETER LicenseSKUID
    LicenseSKUID to assign. Can be used instead of LicenseName
 
    .PARAMETER EnabledServicesDisplayName
    Parameter description
 
    .PARAMETER EnabledServicesName
    Parameter description
 
    .PARAMETER DisabledServicesDisplayName
    Parameter description
 
    .PARAMETER DisabledServicesName
    Parameter description
 
    .EXAMPLE
    Set-O365GroupLicenses -GroupDisplayName 'Test-Group-TestEVOTECPL' -Licenses @(
        New-O365License -LicenseName 'Office 365 E3' -Verbose
        New-O365License -LicenseName 'Enterprise Mobility + Security E5' -Verbose
    ) -Verbose -WhatIf
 
    .EXAMPLE
    Set-O365GroupLicenses -GroupDisplayName 'Test-Group-TestEVOTECPL' -Licenses @(
        New-O365License -LicenseName 'Office 365 E3' -Verbose -DisabledServicesDisplayName 'Microsoft Kaizala Pro', 'Whiteboard (Plan 2)'
        New-O365License -LicenseName 'Enterprise Mobility + Security E5' -Verbose -EnabledServicesDisplayName 'Azure Information Protection Premium P2', 'Microsoft Defender for Identity'
    ) -Verbose -WhatIf
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'ServiceDisplayNameEnable')]
    param(
        [string] $LicenseName,
        [string] $LicenseSKUID,
        [Parameter(ParameterSetName = 'ServiceDisplayNameEnable')][string[]] $EnabledServicesDisplayName,
        [Parameter(ParameterSetName = 'ServiceNameEnable')][string[]] $EnabledServicesName,
        [Parameter(ParameterSetName = 'ServiceDisplayNameDisable')][string[]] $DisabledServicesDisplayName,
        [Parameter(ParameterSetName = 'ServiceNameDisable')][string[]] $DisabledServicesName
    )

    if ($LicenseName) {
        $ServicePlans = Get-O365AzureLicenses -ServicePlans -IncludeLicenseDetails -LicenseName $LicenseName
    } elseif ($LicenseSKUID) {
        $ServicePlans = Get-O365AzureLicenses -ServicePlans -IncludeLicenseDetails -LicenseSKUID $LicenseSKUID
    } else {
        return
    }
    if ($ServicePlans) {
        if ($EnabledServicesDisplayName -or $EnabledServicesName -or $DisabledServicesDisplayName -or $DisabledServicesName) {
            [Array] $DisabledServicePlans = foreach ($Plan in $ServicePlans) {
                if ($EnabledServicesDisplayName) {
                    if ($Plan.ServiceDisplayName -notin $EnabledServicesDisplayName) {
                        $Plan.serviceName
                    }
                } elseif ($EnabledServicesName) {
                    if ($Plan.ServiceName -notin $EnabledServicesName) {
                        $Plan.serviceName
                    }
                } elseif ($DisabledServicesDisplayName) {
                    if ($Plan.ServiceDisplayName -in $DisabledServicesDisplayName) {
                        $Plan.serviceName
                    }
                } elseif ($DisabledServicesName) {
                    if ($Plan.ServiceName -in $DisabledServicesName) {
                        $Plan.serviceName
                    }
                }
            }
        } else {
            $DisabledServicePlans = @()
        }
        if ($ServicePlans[0].LicenseSKUID) {
            [ordered] @{
                accountSkuId         = $ServicePlans[0].LicenseSKUID
                disabledServicePlans = if ($DisabledServicePlans.Count -eq 0) { , @() } else { $DisabledServicePlans }
            }
        } else {
            Write-Warning "New-O365License - No LicenseSKUID found. Skipping"
        }
    }
}
function Set-O365AzureEnterpriseAppsGroupConsent {
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/UserSettings
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter()][bool] $EnableGroupSpecificConsent,
        [Parameter()][string] $GroupId,
        [Parameter()][string] $GroupName,
        # Other options
        [Parameter()][bool] $BlockUserConsentForRiskyApps,
        [Parameter()][bool] $EnableAdminConsentRequests
    )

    $Uri = 'https://graph.microsoft.com/beta/settings/e0953218-a490-4c92-a975-ab724a6cfb07'
    $CurrentSettings = Get-O365AzureEnterpriseAppsGroupConsent -Headers $Headers
    if ($CurrentSettings) {
        [string] $EnableSpecific = if ($PSBoundParameters.ContainsKey('EnableGroupSpecificConsent')) {
            $EnableGroupSpecificConsent.ToString().ToLower()
        } else {
            $CurrentSettings.EnableGroupSpecificConsent.ToString().ToLower()
        }
        if ($PSBoundParameters.ContainsKey('EnableGroupSpecificConsent')) {
            # We only set group if EnableGroupSpecificConsent is used
            if ($GroupId) {
                $Group = $GroupId
            } elseif ($GroupName) {
                $AskForGroup = Get-O365Group -DisplayName $GroupName -Headers $Headers
                if ($AskForGroup.Id) {
                    $Group = $AskForGroup.Id
                    if ($Group -isnot [string]) {
                        Write-Warning -Message "Set-O365AzureEnterpriseAppsGroupConsent - GroupName couldn't be translated to single ID. "
                        foreach ($G in $AskForGroup) {
                            Write-Warning -Message "Group DisplayName: $($G.DisplayName) | Group ID: $($G.ID)"
                        }
                        return
                    }
                } else {
                    Write-Warning -Message "Set-O365AzureEnterpriseAppsGroupConsent - GroupName couldn't be translated to ID. Skipping."
                    return
                }
            } else {
                $Group = ''
            }
        } else {
            # We read the current group
            $Group = $CurrentSettings.ConstrainGroupSpecificConsentToMembersOfGroupId
        }
        [string] $BlockUserConsent = if ($PSBoundParameters.ContainsKey('BlockUserConsentForRiskyApps')) {
            $BlockUserConsentForRiskyApps.ToString().ToLower()
        } else {
            $CurrentSettings.BlockUserConsentForRiskyApps.ToString().ToLower()
        }
        [string] $AdminConsent = if ($PSBoundParameters.ContainsKey('EnableAdminConsentRequests')) {
            $EnableAdminConsentRequests.ToString().ToLower()
        } else {
            $CurrentSettings.EnableAdminConsentRequests.ToString().ToLower()
        }
        $Body = @{
            values = @(
                [ordered] @{ "name" = "EnableGroupSpecificConsent"; "value" = $EnableSpecific }
                [ordered] @{ "name" = "BlockUserConsentForRiskyApps"; "value" = $BlockUserConsent }
                [ordered] @{ "name" = "EnableAdminConsentRequests"; "value" = $AdminConsent }
                [ordered] @{ "name" = "ConstrainGroupSpecificConsentToMembersOfGroupId"; value = $Group }
            )
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
    }
}
function Set-O365AzureEnterpriseAppsUserConsent {
    # https://portal.azure.com/#blade/Microsoft_AAD_IAM/ConsentPoliciesMenuBlade/UserSettings
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][string][ValidateSet('AllowUserConsentForApps', 'AllowUserConsentForSelectedPermissions', 'DoNotAllowUserConsent')] $PermissionGrantPoliciesAssigned
    )

    $Uri = 'https://graph.microsoft.com/v1.0/policies/authorizationPolicy'

    $Convert = Convert-AzureEnterpriseAppsUserConsent -PermissionsGrantPoliciesAssigned $PermissionGrantPoliciesAssigned -Reverse

    $Body = @{
        defaultUserRolePermissions = [ordered] @{
            permissionGrantPoliciesAssigned = if ($Convert) { , @($Convert) } else { , @() }
        }
    }
    if ($Body) {
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
    }
}
function Set-O365AzureEnterpriseAppsUserSettings {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER UsersCanConsentAppsAccessingData
    Parameter description
 
    .PARAMETER UsersCanAddGalleryAppsToMyApp
    Parameter description
 
    .PARAMETER UsersCanOnlySeeO365AppsInPortal
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    Please keep in mind that:
    - Users can consent to apps accessing company data for the groups they own -> can be set using Set-O3465AzureEnterpriseAppsGroupConsent
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $UsersCanConsentAppsAccessingData,
        [nullable[bool]] $UsersCanAddGalleryAppsToMyApp,
        [nullable[bool]] $UsersCanOnlySeeO365AppsInPortal
    )

    $Uri = 'https://main.iam.ad.ext.azure.com/api/EnterpriseApplications/UserSettings'

    # contrary to most of the cmdlets it seem if you provide null as values not filled in, nothing is changed
    # Body "{`"usersCanAllowAppsToAccessData`":false,`"usersCanAddGalleryApps`":null,`"hideOffice365Apps`":null}"
    $Body = @{
        usersCanAllowAppsToAccessData = $UsersCanConsentAppsAccessingData
        usersCanAddGalleryApps        = $UsersCanAddGalleryAppsToMyApp
        hideOffice365Apps             = $UsersCanOnlySeeO365AppsInPortal
    }
    # But we're going to remove those empty entries anyways
    Remove-EmptyValue -Hashtable $Body
    if ($Body.Keys.Count -gt 0) {
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
    }
}
function Set-O365AzureEnterpriseAppsUserSettingsAdmin {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $UserConsentToAppsEnabled
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/RequestApprovals/V2/PolicyTemplates"
    #-Body "{`"id`":null,`"requestExpiresInDays`":30,`"notificationsEnabled`":true,`
    #"remindersEnabled`":true,`"approversV2`":{`"user`":[`"e6a8f1cf-0874-4323-a12f-2bf51bb6dfdd`"],`"group`":[],`"role`":[]}}"
    #$Body = @{
    # Enabled = $UserConsentToAppsEnabled
    #}
    #$null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}

function Set-O365AzureExternalCollaborationSettings {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [ValidateSet('none', 'adminsAndGuestInviters', 'adminsGuestInvitersAndAllMembers', 'everyone')][string] $AllowInvitesFrom,
        [System.Nullable[bool]] $AllowedToSignUpEmailBasedSubscriptions,
        [System.Nullable[bool]] $AllowedToUseSSPR,
        [System.Nullable[bool]] $AllowEmailVerifiedUsersToJoinOrganization,
        [System.Nullable[bool]] $BlockMsolPowerShell,
        [string] $DisplayName,
        [string] $Description,
        [ValidateSet('User', 'GuestUser', 'RestrictedUser')][string] $GuestUserRole,
        [System.Nullable[bool]] $AllowedToCreateApps,
        [System.Nullable[bool]] $AllowedToCreateSecurityGroups,
        [System.Nullable[bool]] $AllowedToReadOtherUsers,
        [Array] $PermissionGrantPoliciesAssigned
    )

    $GuestUserRoleIDs = @{
        'User'           = 'a0b1b346-4d3e-4e8b-98f8-753987be4970'
        'GuestUser'      = '10dae51f-b6af-4016-8d66-8c2a99b929b3'
        'RestrictedUser' = '2af84b1e-32c8-42b7-82bc-daa82404023b'
    }
    if ($GuestUserRole) {
        $GuestUserRoleID = $GuestUserRoleIDs[$GuestUserRole]
    }

    if ($AllowInvitesFrom) {
        # This translation is to make sure the casing is correct as it may be given by user in different way
        if ($AllowInvitesFrom -eq 'none') {
            $AllowInvitesFrom = 'none'
        } elseif ($AllowInvitesFrom -eq 'adminsAndGuestInviters') {
            $AllowInvitesFrom = 'adminsAndGuestInviters'
        } elseif ($AllowInvitesFrom -eq 'adminsGuestInvitersAndAllMembers') {
            $AllowInvitesFrom = 'adminsGuestInvitersAndAllMembers'
        } elseif ($AllowInvitesFrom -eq 'everyone') {
            $AllowInvitesFrom = 'everyone'
        }
    }

    $Uri = 'https://graph.microsoft.com/v1.0/policies/authorizationPolicy'

    $Body = @{
        allowInvitesFrom                          = $AllowInvitesFrom                          # : adminsAndGuestInviters
        allowedToSignUpEmailBasedSubscriptions    = $AllowedToSignUpEmailBasedSubscriptions    # : True
        allowedToUseSSPR                          = $AllowedToUseSSPR                          # : True
        allowEmailVerifiedUsersToJoinOrganization = $AllowEmailVerifiedUsersToJoinOrganization # : False
        blockMsolPowerShell                       = $BlockMsolPowerShell                       # : False
        displayName                               = $DisplayName                               # : Authorization Policy
        description                               = $Description                               # : Used to manage authorization related settings across the company.
        guestUserRoleId                           = $GuestUserRoleId                           # : a0b1b346-4d3e-4e8b-98f8-753987be4970
        defaultUserRolePermissions                = [ordered] @{
            allowedToCreateApps             = $AllowedToCreateApps
            allowedToCreateSecurityGroups   = $AllowedToCreateSecurityGroups
            allowedToReadOtherUsers         = $AllowedToReadOtherUsers
            permissionGrantPoliciesAssigned = $PermissionGrantPoliciesAssigned
        }
    }
    Remove-EmptyValue -Hashtable $Body -Recursive -Rerun 2
    if ($Body) {
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
        #$Output
    }
}
function Set-O365AzureGroupExpiration {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[int]] $GroupLifeTime,
        [string][ValidateSet('None', 'Selected', 'All')] $ExpirationEnabled,
        [string] $AdminNotificationEmails,
        [Array] $ExpirationGroups,
        [Array] $ExpirationGroupsID
    )


    $Uri = 'https://main.iam.ad.ext.azure.com/api/Directories/LcmSettings'

    $CurrentSettings = Get-O365AzureGroupExpiration -Headers $Headers -NoTranslation

    if ($null -ne $GroupLifeTime) {
        # if group lifetime is defined we need to build 2 values
        if ($GroupLifeTime -eq 180) {
            $expiresAfterInDays = 0
            $groupLifetimeCustomValueInDays = 0
        } elseif ($GroupLifeTime -eq 365) {
            $expiresAfterInDays = 1
            $groupLifetimeCustomValueInDays = 0
        } else {
            $expiresAfterInDays = 2
            $groupLifetimeCustomValueInDays = $GroupLifeTime
        }
    } else {
        # if it's not defined we need to get current values
        $expiresAfterInDays = $CurrentSettings.expiresAfterInDays
        $groupLifetimeCustomValueInDays = $CurrentSettings.groupLifetimeCustomValueInDays
    }
    if ($ExpirationEnabled -eq 'None') {
        $ManagedGroupTypes = 2
    } elseif ($ExpirationEnabled -eq 'Selected') {
        $ManagedGroupTypes = 1
    } elseif ($ExpirationEnabled -eq 'All') {
        $ManagedGroupTypes = 0
    } else {
        $ManagedGroupTypes = $CurrentSettings.managedGroupTypes
    }
    if (-not $AdminNotificationEmails) {
        $AdminNotificationEmails = $CurrentSettings.adminNotificationEmails
    }

    if ($ExpirationGroups) {
        [Array] $GroupsID = foreach ($Ex in $ExpirationGroups) {
            $GroupFound = Get-O365Group -DisplayName $Ex -Headers $Headers
            if ($GroupFound.Id) {
                $GroupFound.Id
            }
        }
        if ($GroupsID.Count -gt 0) {
            $groupIdsToMonitorExpirations = if ($GroupsID.Count -in 0, 1) {
                , @($GroupsID)
            } else {
                $GroupsID
            }
        } else {
            Write-Warning -Message "Set-O365AzureGroupExpiration - Couldn't find any groups provided in ExpirationGroups. Skipping"
            return
        }
    } elseif ($ExpirationGroupsID) {
        $groupIdsToMonitorExpirations = if ($ExpirationGroupsID.Count -in 0, 1) {
            , @($ExpirationGroupsID)
        } else {
            $ExpirationGroupsID
        }

    } else {
        $groupIdsToMonitorExpirations = if ($CurrentSettings.groupIdsToMonitorExpirations.count -in 0, 1) {
            , @($CurrentSettings.groupIdsToMonitorExpirations)
        } else {
            $CurrentSettings.groupIdsToMonitorExpirations
        }
    }

    $Body = [ordered] @{
        expiresAfterInDays             = $expiresAfterInDays
        groupLifetimeCustomValueInDays = $groupLifetimeCustomValueInDays
        managedGroupTypesEnum          = $CurrentSettings.managedGroupTypesEnum
        managedGroupTypes              = $ManagedGroupTypes
        adminNotificationEmails        = $AdminNotificationEmails
        groupIdsToMonitorExpirations   = $groupIdsToMonitorExpirations
        policyIdentifier               = $CurrentSettings.policyIdentifier
    }

    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body

}

function Set-O365AzureGroupM365 {
    <#
    .SYNOPSIS
    Enables or Disables Microsoft 365 Groups - "Users can create Microsoft 365 groups in Azure portals, API or PowerShell"
 
    .DESCRIPTION
    Enables or Disables Microsoft 365 Groups - "Users can create Microsoft 365 groups in Azure portals, API or PowerShell"
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER AllowedToCreateM365Groups
    Enables or disables "Users can create Microsoft 365 groups in Azure portals, API or PowerShell"
 
    .EXAMPLE
    Set-O365AzureGroupM365 -Verbose -AllowedToCreateM365Groups $true -WhatIf
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/General
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $AllowedToCreateM365Groups
    )
    $CurrentSettings = Get-O365AzureGroupNamingPolicy -NoTranslation -Headers $Headers
    if ($CurrentSettings.id) {
        $Uri ="https://graph.microsoft.com/beta/settings/$($CurrentSettings.id)"
        [Array] $Values = foreach ($Policy in $CurrentSettings.values) {
            if ($Policy.Name -eq 'EnableGroupCreation') {
                [PSCustomObject] @{
                    name  = 'EnableGroupCreation'
                    value = $AllowedToCreateM365Groups.ToString()
                }
            } else {
                $Policy
            }
        }
        $Body = @{
            values = $Values
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
    }
}
function Set-O365AzureGroupNamingPolicy {
    <#
    .SYNOPSIS
    Sets new prefix/suffix for M365 groups naming policy.
 
    .DESCRIPTION
    Sets new prefix/suffix for M365 groups naming policy. The Microsoft 365 groups naming policy allows you to add a specific prefix and/or suffix to the group name and alias of any Microsoft 365 group created by users. For example: <Finance> <group> <Seattle>
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER Prefix
    Sets or updates the prefix for the group naming policy. One can use words or predefined prefixes.
 
    .PARAMETER Suffix
    Sets or updates the suffix for the group naming policy. One can use words or predefined suffixes.
 
    .PARAMETER RemoveNamingConvention
    Removes the prefix and suffix from the group naming policy.
 
    .EXAMPLE
    Set-O365AzureGroupNamingPolicy -Verbose -Prefix 'O365' -Suffix 'Uops', [Company], 'test' -WhatIf
 
    .EXAMPLE
    Set-O365AzureGroupNamingPolicy -Verbose -Prefix 'O365' -Suffix '' -WhatIf
 
    .EXAMPLE
    Set-O365AzureGroupNamingPolicy -Verbose -RemoveNamingConvention -WhatIf
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/NamingPolicy
    #>

    [cmdletbinding(SupportsShouldProcess, DefaultParameterSetName = 'PrefixSuffix')]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(ParameterSetName = 'PrefixSuffix')][string[]] $Prefix,
        [Parameter(ParameterSetName = 'PrefixSuffix')][string[]] $Suffix,
        [Parameter(ParameterSetName = 'Remove')][switch] $RemoveNamingConvention
    )
    $CurrentSettings = Get-O365AzureGroupNamingPolicy -NoTranslation -Headers $Headers
    if ($CurrentSettings.id) {
        $Uri ="https://graph.microsoft.com/beta/settings/$($CurrentSettings.id)"
        [Array] $Values = foreach ($Policy in $CurrentSettings.values) {
            if ($Policy.Name -eq 'PrefixSuffixNamingRequirement') {
                if ($RemoveNamingConvention) {
                    $ExpectedPolicy = ''
                } else {
                    $ExpectedPolicy = ($Prefix -join "") + "[GroupName]" + ($Suffix -join "")
                }
                [PSCustomObject] @{
                    name  = 'PrefixSuffixNamingRequirement'
                    value = $ExpectedPolicy
                }
            } else {
                $Policy
            }
        }
        $Body = @{
            values = $Values
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
        $Output
    }
}

$Script:ScriptBlockNamingPolicy = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $Static = @(
        '[Department]'
        '[Company]'
        '[Office]'
        '[StateOrProvince]'
        '[CountryOrRegion]'
        '[Title]'
    )

    $Static | Where-Object { $_ -like "*$wordToComplete*" }
}

Register-ArgumentCompleter -CommandName Set-O365AzureGroupNamingPolicy -ParameterName Prefix -ScriptBlock $Script:ScriptBlockNamingPolicy
Register-ArgumentCompleter -CommandName Set-O365AzureGroupNamingPolicy -ParameterName Suffix -ScriptBlock $Script:ScriptBlockNamingPolicy
function Set-O365AzureGroupSecurity {
    <#
    .SYNOPSIS
    Set settings for Security Groups "Users can create security groups in Azure portals, API or PowerShell"
 
    .DESCRIPTION
    Set settings for Security Groups "Users can create security groups in Azure portals, API or PowerShell"
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER AllowedToCreateSecurityGroups
    Enables or Disables - "Users can create security groups in Azure portals, API or PowerShell"
 
    .EXAMPLE
    Set-O365AzureGroupSecurity -Verbose -AllowedToCreateSecurityGroups $true -WhatIf
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/General
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $AllowedToCreateSecurityGroups
    )
    # "https://graph.microsoft.com/beta/policies/authorizationPolicy/authorizationPolicy"
    $Uri = "https://graph.microsoft.com/v1.0/policies/authorizationPolicy"

    $Body = @{
        defaultUserRolePermissions = @{
            allowedToCreateSecurityGroups = $AllowedToCreateSecurityGroups
        }
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
}
function Set-O365AzureGroupSelfService {
    <#
    .SYNOPSIS
    Set settings for Self Service Group Management - "Owners can manage group membership requests in the Access Panel"
 
    .DESCRIPTION
    Set settings for Self Service Group Management - "Owners can manage group membership requests in the Access Panel"
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER OwnersCanManageGroupMembershipRequests
    Enables or disable Self Service Group Management - "Owners can manage group membership requests in the Access Panel"
 
    .PARAMETER RestrictUserAbilityToAccessGroupsFeatures
    Enables or disables Self Service Group Management - "Restrict user ability to access groups features in the Access Panel. Group and User Admin will have read-only access when the value of this setting is 'Yes'."
 
    .EXAMPLE
    Set-O365AzureGroupSelfService -Verbose -OwnersCanManageGroupMembershipRequests $true -WhatIf
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/General
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter()][bool] $OwnersCanManageGroupMembershipRequests,
        [parameter()][bool] $RestrictUserAbilityToAccessGroupsFeatures
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/SsgmProperties/"

    $Body = @{}
    if ($PSBoundParameters.ContainsKey("OwnersCanManageGroupMembershipRequests")) {
        $Body['userDelegationEnabled'] = $OwnersCanManageGroupMembershipRequests
        # It seems that to enable selfServiceGroupManagement one needs to enable userDelegationEnabled which cannot be read
        # from the same URL as the other properties, but selfServiceGroupManagement seems to be the value we need
        #$Body['selfServiceGroupManagementEnabled'] = $OwnersCanManageGroupMembershipRequests
    }
    if ($PSBoundParameters.ContainsKey("RestrictUserAbilityToAccessGroupsFeatures")) {
        $Body['groupsInAccessPanelEnabled'] = -not $RestrictUserAbilityToAccessGroupsFeatures
    }

    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body

}
function Set-O365AzureMultiFactorAuthentication {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER AccountLockoutDurationMinutes
    Minutes until account is automatically unblocked
 
    .PARAMETER AccountLockoutResetMinutes
    Minutes until account lockout counter is reset
 
    .PARAMETER AccountLockoutThreshold
    Number of MFA denials to trigger account lockout
 
    .PARAMETER AllowPhoneMenu
    Parameter description
 
    .PARAMETER BlockForFraud
    Automatically block users who report fraud
 
    .PARAMETER CallerId
    MFA caller ID number (US phone number only)
 
    .PARAMETER DefaultBypassTimespan
    Default one-time bypass seconds
 
    .PARAMETER EnableFraudAlert
    Allow users to submit fraud alerts
 
    .PARAMETER FraudCode
    Code to report fraud during initial greeting
 
    .PARAMETER FraudNotificationEmailAddresses
    Recipient's Email Address
 
    .PARAMETER OneTimeBypassEmailAddresses
    Parameter description
 
    .PARAMETER PinAttempts
    Number of PIN attempts allowed per call
 
    .PARAMETER SayExtensionDigits
    Parameter description
 
    .PARAMETER SmsTimeoutSeconds
    Two-way text message timeout seconds
 
    .PARAMETER Caches
    Parameter description
 
    .PARAMETER Notifications
    Parameter description
 
    .PARAMETER NotificationEmailAddresses
    Parameter description
 
    .PARAMETER Greetings
    Parameter description
 
    .PARAMETER BlockedUsers
    Parameter description
 
    .PARAMETER BypassedUsers
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    Based on: https://portal.azure.com/#blade/Microsoft_AAD_IAM/MultifactorAuthenticationMenuBlade/GettingStarted/fromProviders/
 
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[int]] $AccountLockoutDurationMinutes,
        [nullable[int]] $AccountLockoutCounterResetMinutes,
        [nullable[int]] $AccountLockoutDenialsToTriggerLockout,
        #$AllowPhoneMenu,
        [nullable[bool]] $BlockForFraud,
        #$CallerId,
        #$DefaultBypassTimespan,
        [nullable[bool]] $EnableFraudAlert,
        [nullable[int]] $FraudCode
        #$FraudNotificationEmailAddresses,
        #$OneTimeBypassEmailAddresses,
        #$PinAttempts,
        #$SayExtensionDigits,
        #$SmsTimeoutSeconds,
        #$Caches,
        #$Notifications,
        #$NotificationEmailAddresses
        #$Greetings ,
        #$BlockedUsers ,
        #$BypassedUsers
    )
    #$Uri = "https://main.iam.ad.ext.azure.com/api/MultiFactorAuthentication/GetOrCreateExpandedTenantModel?tenantName=Evotec"
    # $Uri = "https://main.iam.ad.ext.azure.com/api/MultiFactorAuthentication/GetOrCreateExpandedTenantModel"

    # Whatever I do, doesn't work!

    $Uri ="https://main.iam.ad.ext.azure.com/api/MultiFactorAuthentication/TenantModel?licenseKey="
    $Body = [ordered] @{}
    <#
        #tenantId = $CurrentSettings #: ceb371f6
        #licenseKey = $CurrentSettings #:
        #customerId = $CurrentSettings #:
        AllowPhoneMenu = $allowPhoneMenu #: False
        BlockForFraud = $BlockForFraud #: False
        CallerId = $callerId #: 8553308653
        DefaultBypassTimespan = $defaultBypassTimespan #: 300
        EnableFraudAlert = $EnableFraudAlert #: True
        FraudCode = $fraudCode #: 0
        FraudNotificationEmailAddresses = $fraudNotificationEmailAddresses #:
        OneTimeBypassEmailAddresses = $oneTimeBypassEmailAddresses #:
        PinAttempts = $pinAttempts #:
        SayExtensionDigits = $sayExtensionDigits #: False
        SmsTimeoutSeconds = $smsTimeoutSeconds #: 60
        #caches = $caches #: {}
        Notifications = $notifications #:
        NotificationEmailAddresses = $notificationEmailAddresses #: {}
        #greetings = $greetings #: {}
        #blockedUsers = $blockedUsers #: {}
        #bypassedUsers = $bypassedUsers #: {}
        #groups = $groups
        #etag = $etag
    #>

    if ($PSBoundParameters.ContainsKey('AccountLockoutDurationMinutes')) {
        $Body['AccountLockoutDurationMinutes'] = $AccountLockoutDurationMinutes
    }
    if ($PSBoundParameters.ContainsKey('AccountLockoutCounterResetMinutes')) {
        $Body['AccountLockoutResetMinutes'] = $AccountLockoutCounterResetMinutes
    }
    if ($PSBoundParameters.ContainsKey('AccountLockoutDenialsToTriggerLockout')) {
        $Body['AccountLockoutThreshold'] = $AccountLockoutDenialsToTriggerLockout
    }
    if ($PSBoundParameters.ContainsKey('BlockForFraud')) {
        $Body['BlockForFraud'] = $BlockForFraud
    }
    if ($PSBoundParameters.ContainsKey('EnableFraudAlert')) {
        $Body['EnableFraudAlert'] = $EnableFraudAlert
    }
    if ($PSBoundParameters.ContainsKey('FraudCode')) {
        $Body['FraudCode'] = $FraudCode
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
}
function Set-O365AzureProperties {
    <#
    .SYNOPSIS
    Changes the properties of Azure Active Directory (Azure AD) for the current tenant.
 
    .DESCRIPTION
    Changes the properties of Azure Active Directory (Azure AD) for the current tenant - available at URL: https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Properties
 
    .PARAMETER Headers
    Authorization header as created by Connect-O365Admin. If not provided the function will try to fetch it from the current execution context.
 
    .PARAMETER DisplayName
    Name of the tenant
 
    .PARAMETER TechnicalContact
    Technical contact for the tenant
 
    .PARAMETER GlobalPrivacyContact
    Global privacy contact for the tenant
 
    .PARAMETER PrivacyStatementURL
    Privacy statement URL for the tenant
 
    .EXAMPLE
    Set-O365AzureProperties -Verbose -Name "Evotec Test" -TechnicalContact 'test@evotec.pl' -GlobalPrivacyContact 'test@evotec.pl' -PrivacyStatementURL "https://test.pl" -WhatIf
 
    .EXAMPLE
    Set-O365AzureProperties -Verbose -Name "Evotec" -TechnicalContact 'test@evotec.pl' -GlobalPrivacyContact $null -PrivacyStatementURL $null -WhatIf
 
    .NOTES
    Please note that Technical Contact cannot be removed. It always needs to be set.
 
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [alias('Name')][string] $DisplayName,
        [string] $TechnicalContact,
        [string] $GlobalPrivacyContact,
        [string] $PrivacyStatementURL
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories"

    $CurrentSettings = Get-O365AzureProperties -Headers $Headers -NoTranslation
    if ($CurrentSettings) {
        $Body = @{
            objectId                               = $CurrentSettings.objectId                              #
            displayName                            = $CurrentSettings.displayName                           #
            companyLastDirSyncTime                 = $CurrentSettings.companyLastDirSyncTime                #
            dirSyncEnabled                         = $CurrentSettings.dirSyncEnabled                        #
            replicationScope                       = $CurrentSettings.replicationScope                      #
            dataCenterLocation                     = $CurrentSettings.dataCenterLocation                    #
            countryLetterCode                      = $CurrentSettings.countryLetterCode                     #
            countryName                            = $CurrentSettings.countryName                           #
            preferredLanguage                      = $CurrentSettings.preferredLanguage                     #
            preferredLanguages                     = $CurrentSettings.preferredLanguages                    #
            verifiedDomains                        = $CurrentSettings.verifiedDomains                       #
            globalAdminCanManageAzureSubscriptions = $CurrentSettings.globalAdminCanManageAzureSubscriptions#
            privacyProfile                         = $CurrentSettings.privacyProfile                        #
            technicalNotificationMails             = $CurrentSettings.technicalNotificationMails            #
        }
        $Setting = $false
        if ($PSBoundParameters.ContainsKey('DisplayName')) {
            $Body.displayName = $DisplayName
            $Setting = $true
        }
        if ($PSBoundParameters.ContainsKey('TechnicalContact')) {
            if ($TechnicalContact) {
                $Body.technicalNotificationMails = @(
                    $TechnicalContact
                )
            } else {
                Write-Warning -Message "Set-O365AzureProperties - Using empty/null Technical Contact is not supported."
                return
                $Body.technicalNotificationMails = ''
            }
            $Setting = $true
        }
        if ($PSBoundParameters.ContainsKey('GlobalPrivacyContact')) {
            $Body.privacyProfile.contactEmail = $GlobalPrivacyContact
            $Setting = $true
        }
        if ($PSBoundParameters.ContainsKey('PrivacyStatementURL')) {
            $Body.privacyProfile.statementUrl = $PrivacyStatementURL
            $Setting = $true
        }
        if ($Setting) {
            $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
        } else {
            Write-Warning -Message "Set-O365AzureProperties - No settings to update"
        }
    }
}

<#
 
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38"
Invoke-WebRequest -UseBasicParsing -Uri "https://main.iam.ad.ext.azure.com/api/Directories" `
    -Method "PUT" `
    -WebSession $session `
    -Headers @{
    "x-ms-client-session-id" = "2c99a939029b4409ac6028764b17bb03"
    "Accept-Language" = "en"
    "Authorization" = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Imwzc1EtNTBjQ0g0eEJWWkxIVEd3blNSNzY4MCIsImtpZCI6Imwzc1EtNTBjQ0g0eEJWWkxIVEd3blNSNzY4MCJ9.eyJhdWQiOiI3NDY1ODEzNi0xNGVjLTQ2MzAtYWQ5Yi0yNmUxNjBmZjBmYzYiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC84YTg1MjYxMi1lZjc5LTQzNDEtOTdkNy1hMTliMmZhNjRjY2UvIiwiaWF0IjoxNjMzOTYzMDE2LCJuYmYiOjE2MzM5NjMwMTYsImV4cCI6MTYzMzk2NjkxNiwiYWNyIjoiMSIsImFpbyI6IkFWUUFxLzhUQUFBQW5xenkxTURoekJveEhscXVWb3pCRzB4MWsranppc3ZxZ05uOFdYTkk2eXYwYitpMWVja2tNWlJFaDYrMW1KYktSazR6azBxcDBZSjArSnZLK3FuakNQSW9YVHg3SHFERVltcU5LVTNCMDZzPSIsImFtciI6WyJwd2QiLCJtZmEiXSwiYXBwaWQiOiJjNDRiNDA4My0zYmIwLTQ5YzEtYjQ3ZC05NzRlNTNjYmRmM2MiLCJhcHBpZGFjciI6IjIiLCJmYW1pbHlfbmFtZSI6IktseXMiLCJnaXZlbl9uYW1lIjoiUHJ6ZW15c2xhdyIsImlwYWRkciI6Ijg5Ljc3LjEwMi4xNyIsIm5hbWUiOiJQcnplbXlzbGF3IEtseXMgQ0FETSIsIm9pZCI6ImM5NTY5ODhmLTZlZjMtNDEwZi1hNjQyLWVlMTM0ZWY3MjdjMyIsInB1aWQiOiIxMDAzMjAwMTIwNTc0MjdCIiwicmgiOiIwLkFWOEFFaWFGaW5udlFVT1gxNkdiTDZaTXpvTkFTOFN3TzhGSnRIMlhUbFBMM3p4ZkFBUS4iLCJzY3AiOiJ1c2VyX2ltcGVyc29uYXRpb24iLCJzdWIiOiJleUNWMDRhd1dqS0VHMnNUc0dxSmtHSjlZUWJCRzNYazFKcTBpam1CTkc4IiwidGVuYW50X3JlZ2lvbl9zY29wZSI6IkVVIiwidGlkIjoiOGE4NTI2MTItZWY3OS00MzQxLTk3ZDctYTE5YjJmYTY0Y2NlIiwidW5pcXVlX25hbWUiOiJDQURNX0paNVFAZXVyb2ZpbnN0ZXN0My5vbm1pY3Jvc29mdC5jb20iLCJ1cG4iOiJDQURNX0paNVFAZXVyb2ZpbnN0ZXN0My5vbm1pY3Jvc29mdC5jb20iLCJ1dGkiOiJaeEtwdlVRRGVrcWpFV1FucEZVcEFRIiwidmVyIjoiMS4wIiwid2lkcyI6WyI2MmU5MDM5NC02OWY1LTQyMzctOTE5MC0wMTIxNzcxNDVlMTAiLCJiNzlmYmY0ZC0zZWY5LTQ2ODktODE0My03NmIxOTRlODU1MDkiXSwieG1zX3RjZHQiOjE1ODYyNzExNDF9.W0pZUmLN4Xj-o-dWSfJKF1HG08wxvuQEM-mW88EnSKAIVSI6IWAOsWAUWDMdDwuy9DzjjB9u3F_V9ahFWid0t-mn5i0jzswEeg5BanDJOs3sz4KZeKYzXe4Q5eastFKvw526ATAhoQD-lCZ00w7S82vBOB_f5jol_mtMCWopANu5OonTX9glcz2mHxj1_vjNM5NZeOYqCc5Lss8d2XV5ghqauW-mLcLd3hVI6eX_RBKpTm7Y2vgc4EyAte35iGUgPveeFAmF8DyWm1V9BAFqD7lo2dopgXxt3rWXUCw4Zixt6u3fDxpUoIEoAvsInGK-Q8rFbosNyk78Bg6Ja7EzQg"
    "x-ms-effective-locale" = "en.en-us"
    "Accept" = "*/*"
    "Referer" = ""
    "x-ms-client-request-id" = "c465c06e-6937-40ba-88aa-ba63dc35c01e"
} `
    -ContentType "application/json" `
    -Body ([System.Text.Encoding]::UTF8.GetBytes("
 
    {`"objectId`":`"8a852612-ef79-4341-97d7-a19b2fa64cce`",
    `"displayName`":`"Eurofins GSC France`",
    `"companyLastDirSyncTime`":`"2021-10-11T14:42:59Z`",
    `"dirSyncEnabled`":true,`"replicationScope`":`"EU`",
    `"dataCenterLocation`":`"EU Model Clause compliant datacenters`",
    `"countryLetterCode`":`"FR`",
    `"countryName`":`"France`",`"preferredLanguage`":`"en`",
    `"preferredLanguages`":[{`"displayName`":`"English`",`"languageCode`":`"en`"},
    {`"displayName`":`"fran$([char]231)ais`",`"languageCode`":`"fr`"}],
    `"verifiedDomains`":[{`"id`":`"000520000819CC42`",`"type`":`"Managed`",`"name`":
    `"eurofinstest3.onmicrosoft.com`",`"initial`":true,`"isDirSyncExchangeOnlineDomain`":false},
    {`"id`":`"000520000866EC97`",`"type`":`"Managed`",`"name`":`"eurofinstest3.mail.onmicrosoft.com`",
    `"initial`":false,`"isDirSyncExchangeOnlineDomain`":true},{`"id`":`"000520000A1A4828`",`"type`":`"Managed`",
    `"name`":`"eurofins-test.com`",`"initial`":false,`"isDirSyncExchangeOnlineDomain`":false}],
    `"globalAdminCanManageAzureSubscriptions`":false,
    `"privacyProfile`":{`"contactEmail`":`"`",`"statementUrl`":`"https://eurofins.com`"},
    `"technicalNotificationMails`":[`"aurelienlesage@eurofins.com`"]}"))
 
#>

function Set-O365AzurePropertiesSecurity {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $EnableSecurityDefaults
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/SecurityDefaults/UpdateSecurityDefaultOnSave?enableSecurityDefaults=$EnableSecurityDefaults"

    $CurrentSettings = Get-O365AzurePropertiesSecurity -Headers $Headers
    if ($CurrentSettings.anyClassicPolicyEnabled -eq $false) {
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT
    } elseif ($CurrentSettings.anyClassicPolicyEnabled -eq $true) {
        Write-Warning -Message "Set-O365AzurePropertiesSecurity - It looks like you have Classic policies enabled. Enabling Classic policies prevents you from enabling Security defaults."
    }
}
function Set-O365AzureUserSettings {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER UsersCanRegisterApps
    Parameter description
 
    .PARAMETER RestrictNonAdminUsers
    Parameter description
 
    .PARAMETER LinkedInAccountConnection
    Parameter description
 
    .PARAMETER LinkedInSelectedGroupObjectId
    Parameter description
 
    .PARAMETER LinkedInSelectedGroupDisplayName
    Parameter description
 
    .EXAMPLE
    Set-O365UserSettings -RestrictNonAdminUsers $true -LinkedInAccountConnection $true -LinkedInSelectedGroupObjectId 'b6cdb9c3-d660-4558-bcfd-82c14a986b56'
 
    .EXAMPLE
    Set-O365UserSettings -RestrictNonAdminUsers $true -LinkedInAccountConnection $true -LinkedInSelectedGroupDisplayName 'All Users'
 
    .EXAMPLE
    Set-O365UserSettings -RestrictNonAdminUsers $true -LinkedInAccountConnection $false
 
    .EXAMPLE
    Set-O365UserSettings -RestrictNonAdminUsers $true
 
    .NOTES
    https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/UserSettings
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $UsersCanRegisterApps,
        [nullable[bool]] $RestrictNonAdminUsers,
        [nullable[bool]] $LinkedInAccountConnection,
        [string] $LinkedInSelectedGroupObjectId,
        [string] $LinkedInSelectedGroupDisplayName
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/Directories/PropertiesV2"

    $Body = @{
        usersCanRegisterApps  = $UsersCanRegisterApps
        restrictNonAdminUsers = $RestrictNonAdminUsers
    }
    Remove-EmptyValue -Hashtable $Body

    if ($null -ne $LinkedInAccountConnection) {
        if ($LinkedInAccountConnection -eq $true -and $linkedInSelectedGroupObjectId) {
            $Body.enableLinkedInAppFamily = 4
            $Body.linkedInSelectedGroupObjectId = $linkedInSelectedGroupObjectId
        } elseif ($LinkedInAccountConnection -eq $true -and $LinkedInSelectedGroupDisplayName) {
            $Body.enableLinkedInAppFamily = 4
            $Body.linkedInSelectedGroupDisplayName = $LinkedInSelectedGroupDisplayName
        } elseif ($LinkedInAccountConnection -eq $true) {
            $Body.enableLinkedInAppFamily = 0
            $Body.linkedInSelectedGroupObjectId = $null
        } elseif ($LinkedInAccountConnection -eq $false) {
            $Body.enableLinkedInAppFamily = 1
            $Body.linkedInSelectedGroupObjectId = $null
        }
    }
    if ($Body.Keys.Count -gt 0) {
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
        # $Output
    }
}
function Set-O365BillingLicenseAutoClaim {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [bool] $EnableAutoClaim
    )
    $Uri ="https://admin.microsoft.com/fd/m365licensing/v1/policies/autoclaim"

    if ($PSBoundParameters.ContainsKey("EnableAutoClaim")) {
        $Body = [ordered] @{
            policyValue = if ($EnableAutoClaim -eq $true) { "Enabled" } else { "Disabled" }
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    }
}
function Set-O365BillingNotifications {
    <#
    .SYNOPSIS
    Sets settijngs for Billing notifications (Invoice PDF ON/OFF)
 
    .DESCRIPTION
    Sets settijngs for Billing notifications (Invoice PDF ON/OFF)
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER SendInvoiceEmails
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    Sets settings for Billing notifications https://admin.microsoft.com/#/BillingNotifications
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $SendInvoiceEmails
    )
    $Uri ="https://admin.microsoft.com/fd/commerceMgmt/mgmtsettings/invoicePreference?api-version=1.0"

    $Body = @{
        sendInvoiceEmails = $SendInvoiceEmails
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    if ($Output.setInvoicePreferenceSuccessful -eq $true) {

    }
}
function Set-O365GroupLicenses {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter()][string] $GroupID,
        [parameter()][alias('GroupName')][string] $GroupDisplayName,
        [Array] $Licenses
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/AccountSkus/assignUpdateRemove"

    if ($GroupID) {
        $Group = $GroupID
        #$GroupSearch = Get-O365Group -Id $GroupID
        #if ($GroupSearch.id) {
        # $GroupName = $GroupSearch.displayName
        #}
    } elseif ($GroupDisplayName) {
        $GroupSearch = Get-O365Group -DisplayName $GroupDisplayName
        if ($GroupSearch.id) {
            $Group = $GroupSearch.id
            #$GroupName = $GroupSearch.displayName
        }
    }
    if ($Group) {
        $CurrentLicenses = Get-O365GroupLicenses -GroupID $Group -NoTranslation
        if ($CurrentLicenses.objectid) {
            # we cache it for better use of search
            $CacheLicenses = [ordered] @{}
            foreach ($License in $CurrentLicenses.licenses) {
                $CacheLicenses[$License.accountSkuId] = $License
            }

            <#
            accountSkuId disabledServicePlans hasErrors errorCount
            ------------ -------------------- --------- ----------
            evotecpoland:FLOW_FREE {} 0
            evotecpoland:POWER_BI_STANDARD {} 0
            evotecpoland:POWER_BI_PRO {} 0
            evotecpoland:ENTERPRISEPACK {POWER_VIRTUAL_AGENTS_O365_P2, PROJECT_O365_P2} 0
            #>

            $AddLicenses = [System.Collections.Generic.List[System.Collections.IDictionary]]::new()
            $RemoveLicenses = [System.Collections.Generic.List[string]]::new()
            $UpdateLicenses = [System.Collections.Generic.List[System.Collections.IDictionary]]::new()

            foreach ($License in $Licenses) {
                if ($CacheLicenses[$License.accountSkuId]) {
                    if (-not (Compare-Object -ReferenceObject $License.disabledServicePlans -DifferenceObject $CacheLicenses[$License.accountSkuId].disabledServicePlans)) {
                        # We do nothing, because the licenses have the same disabled service plans are the same
                    } else {
                        $UpdateLicenses.Add($License)
                    }
                } else {
                    $AddLicenses.Add($License)
                }
            }
            foreach ($License in $CurrentLicenses.licenses) {
                if ($License.accountSkuId -notin $Licenses.accountSkuId) {
                    #$PrepareForRemoval = New-O365License -DisabledServicesName $License.disabledServicePlans -LicenseSKUID $License.accountSkuId
                    #if ($PrepareForRemoval) {
                    $RemoveLicenses.Add($License.accountSkuId)
                    #}
                }
            }

            $Body = [ordered] @{
                assignments = @(
                    [ordered] @{
                        objectId       = $Group
                        #displayName = $GroupName
                        isUser         = $false
                        addLicenses    = $AddLicenses
                        removeLicenses = $RemoveLicenses
                        updateLicenses = $UpdateLicenses
                    }
                )
            }
            $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
            $Output

        } else {
            Write-Warning -Message "Set-O365GroupLicenses - Querying for current group licenses failed. Skipping."
        }
    } else {
        Write-Error -Message "Set-O365GroupLicenses - Couldn't find group. Skipping."
    }
}
function Set-O365OrgAzureSpeechServices {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [bool] $AllowTheOrganizationWideLanguageModel
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/azurespeechservices"

    $Body = @{
        isTenantEnabled = $AllowTheOrganizationWideLanguageModel
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgBingDataCollection {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $IsBingDataCollectionConsented
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/security/bingdatacollection"

    $Body = [ordered] @{
        IsBingDataCollectionConsented = $IsBingDataCollectionConsented
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgBookings {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $Enabled,
        [nullable[bool]] $ShowPaymentsToggle,
        [nullable[bool]] $PaymentsEnabled,
        [nullable[bool]] $ShowSocialSharingToggle,
        [nullable[bool]] $SocialSharingRestricted,
        [nullable[bool]] $ShowBookingsAddressEntryRestrictedToggle,
        [nullable[bool]] $BookingsAddressEntryRestricted,
        [nullable[bool]] $ShowBookingsAuthEnabledToggle,
        [nullable[bool]] $BookingsAuthEnabled,
        [nullable[bool]] $ShowBookingsCreationOfCustomQuestionsRestrictedToggle,
        [nullable[bool]] $BookingsCreationOfCustomQuestionsRestricted,
        [nullable[bool]] $ShowBookingsExposureOfStaffDetailsRestrictedToggle,
        [nullable[bool]] $BookingsExposureOfStaffDetailsRestricted,
        [nullable[bool]] $ShowBookingsNotesEntryRestrictedToggle,
        [nullable[bool]] $BookingsNotesEntryRestricted,
        [nullable[bool]] $ShowBookingsPhoneNumberEntryRestrictedToggle,
        [nullable[bool]] $BookingsPhoneNumberEntryRestricted,
        [nullable[bool]] $ShowStaffApprovalsToggle,
        [nullable[bool]] $StaffMembershipApprovalRequired
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/bookings"

    $CurrentSettings = Get-O365OrgBookings -Headers $Headers
    if ($CurrentSettings) {
        $Body = @{
            Enabled                                               = $CurrentSettings.Enabled                                               #: True
            ShowPaymentsToggle                                    = $CurrentSettings.ShowPaymentsToggle                                    #: False
            PaymentsEnabled                                       = $CurrentSettings.PaymentsEnabled                                       #: False
            ShowSocialSharingToggle                               = $CurrentSettings.ShowSocialSharingToggle                               #: True
            SocialSharingRestricted                               = $CurrentSettings.SocialSharingRestricted                               #: False
            ShowBookingsAddressEntryRestrictedToggle              = $CurrentSettings.ShowBookingsAddressEntryRestrictedToggle              #: False
            BookingsAddressEntryRestricted                        = $CurrentSettings.BookingsAddressEntryRestricted                        #: False
            ShowBookingsAuthEnabledToggle                         = $CurrentSettings.ShowBookingsAuthEnabledToggle                         #: False
            BookingsAuthEnabled                                   = $CurrentSettings.BookingsAuthEnabled                                   #: False
            ShowBookingsCreationOfCustomQuestionsRestrictedToggle = $CurrentSettings.ShowBookingsCreationOfCustomQuestionsRestrictedToggle #: False
            BookingsCreationOfCustomQuestionsRestricted           = $CurrentSettings.BookingsCreationOfCustomQuestionsRestricted           #: False
            ShowBookingsExposureOfStaffDetailsRestrictedToggle    = $CurrentSettings.ShowBookingsExposureOfStaffDetailsRestrictedToggle    #: False
            BookingsExposureOfStaffDetailsRestricted              = $CurrentSettings.BookingsExposureOfStaffDetailsRestricted              #: False
            ShowBookingsNotesEntryRestrictedToggle                = $CurrentSettings.ShowBookingsNotesEntryRestrictedToggle                #: False
            BookingsNotesEntryRestricted                          = $CurrentSettings.BookingsNotesEntryRestricted                          #: False
            ShowBookingsPhoneNumberEntryRestrictedToggle          = $CurrentSettings.ShowBookingsPhoneNumberEntryRestrictedToggle          #: False
            BookingsPhoneNumberEntryRestricted                    = $CurrentSettings.BookingsPhoneNumberEntryRestricted                    #: False
            ShowStaffApprovalsToggle                              = $CurrentSettings.ShowStaffApprovalsToggle                              #: True
            StaffMembershipApprovalRequired                       = $CurrentSettings.StaffMembershipApprovalRequired                       #: False
        }

        if ($null -ne $Enabled) {
            $Body.Enabled = $Enabled
        }
        if ($null -ne $ShowPaymentsToggle) {
            $Body.ShowPaymentsToggle = $ShowPaymentsToggle
        }
        if ($null -ne $PaymentsEnabled) {
            $Body.PaymentsEnabled = $PaymentsEnabled
        }
        if ($null -ne $ShowSocialSharingToggle) {
            $Body.ShowSocialSharingToggle = $ShowSocialSharingToggle
        }
        if ($null -ne $SocialSharingRestricted) {
            $Body.SocialSharingRestricted = $SocialSharingRestricted
        }
        if ($null -ne $ShowBookingsAddressEntryRestrictedToggle) {
            $Body.ShowBookingsAddressEntryRestrictedToggle = $ShowBookingsAddressEntryRestrictedToggle
        }
        if ($null -ne $BookingsAddressEntryRestricted) {
            $Body.BookingsAddressEntryRestricted = $BookingsAddressEntryRestricted
        }
        if ($null -ne $ShowBookingsAuthEnabledToggle) {
            $Body.ShowBookingsAuthEnabledToggle = $ShowBookingsAuthEnabledToggle
        }
        if ($null -ne $BookingsAuthEnabled) {
            $Body.BookingsAuthEnabled = $BookingsAuthEnabled
        }
        if ($null -ne $ShowBookingsCreationOfCustomQuestionsRestrictedToggle) {
            $Body.ShowBookingsCreationOfCustomQuestionsRestrictedToggle = $ShowBookingsCreationOfCustomQuestionsRestrictedToggle
        }
        if ($null -ne $BookingsCreationOfCustomQuestionsRestricted) {
            $Body.BookingsCreationOfCustomQuestionsRestricted = $BookingsCreationOfCustomQuestionsRestricted
        }
        if ($null -ne $ShowBookingsExposureOfStaffDetailsRestrictedToggle) {
            $Body.ShowBookingsExposureOfStaffDetailsRestrictedToggle = $ShowBookingsExposureOfStaffDetailsRestrictedToggle
        }
        if ($null -ne $BookingsExposureOfStaffDetailsRestricted) {
            $Body.BookingsExposureOfStaffDetailsRestricted = $BookingsExposureOfStaffDetailsRestricted
        }
        if ($null -ne $ShowBookingsNotesEntryRestrictedToggle) {
            $Body.ShowBookingsNotesEntryRestrictedToggle = $ShowBookingsNotesEntryRestrictedToggle
        }
        if ($null -ne $BookingsNotesEntryRestricted) {
            $Body.BookingsNotesEntryRestricted = $BookingsNotesEntryRestricted
        }
        if ($null -ne $ShowBookingsPhoneNumberEntryRestrictedToggle) {
            $Body.ShowBookingsPhoneNumberEntryRestrictedToggle = $ShowBookingsPhoneNumberEntryRestrictedToggle
        }
        if ($null -ne $BookingsPhoneNumberEntryRestricted) {
            $Body.BookingsPhoneNumberEntryRestricted = $BookingsPhoneNumberEntryRestricted
        }
        if ($null -ne $ShowStaffApprovalsToggle) {
            $Body.ShowStaffApprovalsToggle = $ShowStaffApprovalsToggle
        }
        if ($null -ne $StaffMembershipApprovalRequired) {
            $Body.StaffMembershipApprovalRequired = $StaffMembershipApprovalRequired
        }
        Remove-EmptyValue -Hashtable $Body
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgBriefingEmail {
    <#
    .SYNOPSIS
    Let people in your organization receive Briefing Email
 
    .DESCRIPTION
    Let people in your organization receive Briefing Email
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER SubscribeByDefault
    Subscribes or unsubscribes people in your organization to receive Briefing Email
 
    .EXAMPLE
    An example
 
    .NOTES
    Users will receive Briefing email by default, but can unsubscribe at any time from their Briefing email or Briefing settings page. Email is only sent to users if their Office 365 language is English or Spanish.
 
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        #[bool] $MailEnable,
        [bool] $SubscribeByDefault
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/briefingemail"

    $Body = @{
        value = @{
            IsSubscribedByDefault = $SubscribeByDefault
        }
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgCalendarSharing {
    <#
    .SYNOPSIS
    Let your users share their calendars with people outside of your organization who have Office 365 or Exchange
 
    .DESCRIPTION
    Let your users share their calendars with people outside of your organization who have Office 365 or Exchange
 
    .PARAMETER Headers
    Authentication Token along with additional information that is created with Connect-O365Admin. If heaaders are not provided it will use the default token.
 
    .PARAMETER EnableAnonymousCalendarSharing
    Enables or Disables anonymous calendar sharing
 
    .PARAMETER EnableCalendarSharing
    Enables or Disables calendar sharing
 
    .PARAMETER SharingOption
    Decide on how to share the calendar
    - Show calendar free/busy information with time only (CalendarSharingFreeBusySimple)
    - Show calendar free/busy information with time, subject and location (CalendarSharingFreeBusyDetail)
    - Show all calendar appointment information (CalendarSharingFreeBusyReviewer)
 
    .EXAMPLE
    Set-O365CalendarSharing -EnableCalendarSharing $false
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $EnableAnonymousCalendarSharing,
        [nullable[bool]] $EnableCalendarSharing,
        [string][ValidateSet('CalendarSharingFreeBusyDetail', 'CalendarSharingFreeBusySimple', 'CalendarSharingFreeBusyReviewer')] $SharingOption
    )
    # We need to get current settings because it always requires all parameters
    # If we would just provide one parameter it would reset everything else
    $CurrentSettings = Get-O365OrgCalendarSharing -Headers $Headers
    $Body = [ordered] @{
        ContractIdentity               = $CurrentSettings.ContractIdentity
        EnableAnonymousCalendarSharing = $CurrentSettings.EnableAnonymousCalendarSharing
        EnableCalendarSharing          = $CurrentSettings.EnableCalendarSharing
        SharingOption                  = $CurrentSettings.SharingOption
    }
    if ($null -ne $EnableAnonymousCalendarSharing) {
        $Body.EnableAnonymousCalendarSharing = $EnableAnonymousCalendarSharing
    }
    if ($null -ne $EnableCalendarSharing) {
        $Body.EnableCalendarSharing = $EnableCalendarSharing
    }
    if ($SharingOption) {
        $Body.SharingOption = $SharingOption
    }
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/calendarsharing"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgCommunicationToUsers {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $ServiceEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/EndUserCommunications"

    $Body = @{
        ServiceEnabled = $ServiceEnabled
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body #-WhatIf:$WhatIfPreference.IsPresent
    $Output
}
function Set-O365OrgCortana {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER Enabled
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $Enabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/cortana"

    $Body = @{
        Enabled = $Enabled
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgCustomerLockbox {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $RequireApproval
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/security/dataaccess"
    $Body = @{
        RequireApproval = $RequireApproval
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgDynamics365ConnectionGraph {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $ServiceEnabled,
        [string] $ConnectionGraphUsersExclusionGroup
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/dcg"

    $Body = @{
        ServiceEnabled                     = $ServiceEnabled
        ConnectionGraphUsersExclusionGroup = $ConnectionGraphUsersExclusionGroup
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgDynamics365CustomerVoice {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter()][bool] $ReduceSurveyFatigueEnabled,
        [Parameter()][int] $ReduceSurveyFatigueDays,
        [Parameter()][Array] $CustomDomainEmails,
        [Parameter()][bool] $PreventPhishingAttemptsEnabled,
        [Parameter()][bool] $CollectNamesEnabled,
        [Parameter()][bool] $RestrictSurveyAccessEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/officeformspro"

    $CurrentSettings = Get-O365OrgDynamics365CustomerVoice -Headers $Headers -NoTranslation

    $Body = [ordered] @{
        "RecordIdentityByDefaultEnabled" = $CurrentSettings.RecordIdentityByDefaultEnabled
        "InOrgFormsPhishingScanEnabled"  = $CurrentSettings.InOrgFormsPhishingScanEnabled
        "OverSurveyManagementEnabled"    = $CurrentSettings.OverSurveyManagementEnabled
        "OverSurveyManagementDays"       = $CurrentSettings.OverSurveyManagementDays
        "CustomDomainEmails"             = $CurrentSettings.CustomDomainEmails
        "RestrictSurveyAccessEnabled"    = $CurrentSettings.RestrictSurveyAccessEnabled
    }

    if ($PSBoundParameters.ContainsKey("ReduceSurveyFatigueEnabled")) {
        $Body["OverSurveyManagementEnabled"] = $ReduceSurveyFatigueEnabled
    }
    if ($PSBoundParameters.ContainsKey("ReduceSurveyFatigueDays")) {
        $Body["OverSurveyManagementDays"] = $ReduceSurveyFatigueDays
    }
    if ($PSBoundParameters.ContainsKey("CustomDomainEmails")) {
        $Body["CustomDomainEmails"] = $CustomDomainEmails
    }
    if ($PSBoundParameters.ContainsKey("PreventPhishingAttemptsEnabled")) {
        $Body["InOrgFormsPhishingScanEnabled"] = $PreventPhishingAttemptsEnabled
    }
    if ($PSBoundParameters.ContainsKey("CollectNamesEnabled")) {
        $Body["RecordIdentityByDefaultEnabled"] = $CollectNamesEnabled
    }
    if ($PSBoundParameters.ContainsKey("RestrictSurveyAccessEnabled")) {
        $Body["RestrictSurveyAccessEnabled"] = $RestrictSurveyAccessEnabled
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgDynamics365SalesInsights {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $ServiceEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/dci"

    $Body = @{
        ServiceEnabled = $ServiceEnabled
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgForms {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $BingImageSearchEnabled,
        [nullable[bool]] $ExternalCollaborationEnabled,
        [nullable[bool]] $ExternalSendFormEnabled,
        [nullable[bool]] $ExternalShareCollaborationEnabled,
        [nullable[bool]] $ExternalShareTemplateEnabled,
        [nullable[bool]] $ExternalShareResultEnabled,
        [nullable[bool]] $InOrgFormsPhishingScanEnabled,
        [nullable[bool]] $InOrgSurveyIncentiveEnabled,
        [nullable[bool]] $RecordIdentityByDefaultEnabled
    )
    # We need to get current settings because it always requires all parameters
    # If we would just provide one parameter it would reset everything else
    $CurrentSettings = Get-O365OrgForms -Headers $Headers
    $Body = [ordered] @{
        BingImageSearchEnabled            = $CurrentSettings.BingImageSearchEnabled
        ExternalCollaborationEnabled      = $CurrentSettings.ExternalCollaborationEnabled
        ExternalSendFormEnabled           = $CurrentSettings.ExternalSendFormEnabled
        ExternalShareCollaborationEnabled = $CurrentSettings.ExternalShareCollaborationEnabled
        ExternalShareTemplateEnabled      = $CurrentSettings.ExternalShareTemplateEnabled
        ExternalShareResultEnabled        = $CurrentSettings.ExternalShareResultEnabled
        InOrgFormsPhishingScanEnabled     = $CurrentSettings.InOrgFormsPhishingScanEnabled
        InOrgSurveyIncentiveEnabled       = $CurrentSettings.InOrgSurveyIncentiveEnabled
        RecordIdentityByDefaultEnabled    = $CurrentSettings.RecordIdentityByDefaultEnabled
    }
    if ($null -ne $BingImageSearchEnabled) {
        $Body.BingImageSearchEnabled = $BingImageSearchEnabled
    }
    if ($null -ne $ExternalCollaborationEnabled) {
        $Body.ExternalCollaborationEnabled = $ExternalCollaborationEnabled
    }
    if ($null -ne $ExternalSendFormEnabled) {
        $Body.ExternalSendFormEnabled = $ExternalSendFormEnabled
    }
    if ($null -ne $ExternalShareCollaborationEnabled) {
        $Body.ExternalShareCollaborationEnabled = $ExternalShareCollaborationEnabled
    }
    if ($null -ne $ExternalShareTemplateEnabled) {
        $Body.ExternalShareTemplateEnabled = $ExternalShareTemplateEnabled
    }
    if ($null -ne $ExternalShareResultEnabled) {
        $Body.ExternalShareResultEnabled = $ExternalShareResultEnabled
    }
    if ($null -ne $InOrgFormsPhishingScanEnabled) {
        $Body.InOrgFormsPhishingScanEnabled = $InOrgFormsPhishingScanEnabled
    }
    if ($null -ne $InOrgSurveyIncentiveEnabled) {
        $Body.InOrgSurveyIncentiveEnabled = $InOrgSurveyIncentiveEnabled
    }
    if ($null -ne $RecordIdentityByDefaultEnabled) {
        $Body.RecordIdentityByDefaultEnabled = $RecordIdentityByDefaultEnabled
    }

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/officeforms"
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgGraphDataConnect {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER ServiceEnabled
    Parameter description
 
    .PARAMETER TenantLockBoxApproverGroup
    Group provided in form of email address. The email address must exists! Otherwise the api will break cmdlet
 
    .PARAMETER Force
    Forces the operation to run ignoring current settings. Useful to overwrite settings after breaking tenant :-)
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $ServiceEnabled,
        [string] $TenantLockBoxApproverGroup,
        [switch] $Force
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/o365dataplan"

    if ($TenantLockBoxApproverGroup -and $TenantLockBoxApproverGroup -notlike "*@*") {
        Write-Warning -Message "Set-O365OrgGraphDataConnect - TenantLockBoxApproverGroup must be given in email format, and it must exists."
        return
    }

    if (-not $Force) {
        $CurrentSettings = Get-O365OrgGraphDataConnect -Headers $Headers
        if ($CurrentSettings) {
            $Body = @{
                "ServiceEnabled"             = $CurrentSettings.ServiceEnabled
                "TenantLockBoxApproverGroup" = $CurrentSettings.TenantLockBoxApproverGroup
            }

            if ($null -ne $ServiceEnabled) {
                $Body.ServiceEnabled = $ServiceEnabled
            }
            if ($TenantLockBoxApproverGroup) {
                $Body.TenantLockBoxApproverGroup = $TenantLockBoxApproverGroup
            }
        }
    } else {
        $Body = @{
            "ServiceEnabled"             = $ServiceEnabled
            "TenantLockBoxApproverGroup" = $TenantLockBoxApproverGroup
        }
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}

function Set-O365OrgHelpdeskInformation {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [alias('Enabled')][bool] $CustomHelpDeskInformationEnabled,
        [string] $Title,
        [string] $PhoneNumber,
        [string] $EmailAddress,
        [string] $SupportUrl,
        [string] $SupportUrlTitle
    )
    $Uri ="https://admin.microsoft.com/admin/api/Settings/company/helpdesk"

    $CurrentSettings = Get-O365OrgHelpdeskInformation -Headers $Headers
    if ($CurrentSettings) {

        $Body = @{
            "CustomSupportEnabled" = $CurrentSettings.CustomSupportEnabled
            "Title"                = $CurrentSettings.Title

            "PhoneEnabled"         = $CurrentSettings.PhoneEnabled
            "PhoneNumber"          = $CurrentSettings.PhoneNumber

            "EmailEnabled"         = $CurrentSettings.EmailEnabled
            "EmailAddress"         = $CurrentSettings.EmailAddress

            "UrlEnabled"           = $CurrentSettings.UrlEnabled
            "SupportUrl"           = $CurrentSettings.SupportUrl
            "SupportUrlTitle"      = $CurrentSettings.SupportUrlTitle
        }

        if ($PSBoundParameters.ContainsKey('CustomHelpDeskInformationEnabled')) {
            $Body.CustomSupportEnabled = $CustomHelpDeskInformationEnabled
        }
        if ($PSBoundParameters.ContainsKey('Title')) {
            $Body.Title = $Title
        }
        if ($PSBoundParameters.ContainsKey('PhoneNumber')) {
            $Body.PhoneNumber = $PhoneNumber
            $Body.PhoneEnabled = if ($PhoneNumber) { $true } else { $false }
        }
        if ($PSBoundParameters.ContainsKey('EmailAddress')) {
            $Body.EmailEnabled = if ($EmailAddress) { $true } else { $false }
            $Body.EmailAddress = $EmailAddress
        }
        if ($PSBoundParameters.ContainsKey('SupportUrl')) {
            $Body.SupportUrlTitle = $SupportUrlTitle
            $Body.SupportUrl = $SupportUrl
            $Body.UrlEnabled = if ($SupportUrl) { $true } else { $false }
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgInstallationOptions {
    <#
    .SYNOPSIS
    Choose how often users get feature updates and the Microsoft apps that users can install on their own devices.
 
    .DESCRIPTION
    Choose how often users get feature updates and the Microsoft apps that users can install on their own devices.
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER WindowsBranch
    Enable/Disable Windows
 
    .PARAMETER WindowsOffice
    Parameter description
 
    .PARAMETER WindowsSkypeForBusiness
    Parameter description
 
    .PARAMETER MacOffice
    Parameter description
 
    .PARAMETER MacSkypeForBusiness
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
    It takes a while for GUI to report these changes. Be patient.
 
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string][ValidateSet('CurrentChannel', 'MonthlyEnterpriseChannel', 'SemiAnnualEnterpriseChannel')] $WindowsBranch,
        [nullable[bool]] $WindowsOffice,
        [nullable[bool]] $WindowsSkypeForBusiness,
        [nullable[bool]] $MacOffice,
        [nullable[bool]] $MacSkypeForBusiness
    )
    $ReverseBranches = @{
        "CurrentChannel"              = 1
        "MonthlyEnterpriseChannel"    = 3
        "SemiAnnualEnterpriseChannel" = 2
    }

    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/usersoftware"

    $CurrentSettings = Get-O365OrgInstallationOptions -NoTranslation -Headers $Headers
    if ($CurrentSettings) {
        $Body = @{
            UserSoftwareSettings = $CurrentSettings
        }

        if ($WindowsBranch) {
            $Body.UserSoftwareSettings[0].Branch = $ReverseBranches[$WindowsBranch]
            # we probably should update "BranchLastUpdateTime": "2021-09-02T21:54:02.953Z",
            # but I am not sure if it matters
        }
        if ($null -ne $WindowsOffice) {
            $Body.UserSoftwareSettings[0].ServiceStatusMap.'Office (includes Skype for Business),MicrosoftOffice_ClientDownload' = $WindowsOffice
        }
        if ($null -ne $WindowsSkypeForBusiness) {
            $Body.UserSoftwareSettings[0].ServiceStatusMap.'Skype for Business (Standalone),MicrosoftCommunicationsOnline' = $WindowsSkypeForBusiness
        }
        if ($null -ne $MacOffice) {
            $Body.UserSoftwareSettings[1].ServiceStatusMap.'Office,MicrosoftOffice_ClientDownload' = $MacOffice
        }
        if ($null -ne $MacSkypeForBusiness) {
            $Body.UserSoftwareSettings[1].LegacyServiceStatusMap.'Skype for Business (X EI Capitan 10.11 or higher),MicrosoftCommunicationsOnline' = $MacSkypeForBusiness
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgM365Groups {
    <#
    .SYNOPSIS
    Choose how guests from outside your organization can collaborate with your users in Microsoft 365 Groups. Learn more about guest access to Microsoft 365 Groups
 
    .DESCRIPTION
    Choose how guests from outside your organization can collaborate with your users in Microsoft 365 Groups. Learn more about guest access to Microsoft 365 Groups
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER AllowGuestAccess
    PLet group owners add people outside your organization to Microsoft 365 Groups as guests
 
    .PARAMETER AllowGuestsAsMembers
    Let guest group members access group content
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $AllowGuestAccess,
        [nullable[bool]] $AllowGuestsAsMembers
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/security/o365guestuser"

    $CurrentSettings = Get-O365OrgM365Groups -Headers $Headers
    $Body = [ordered] @{
        AllowGuestAccess     = $CurrentSettings.AllowGuestAccess
        AllowGuestsAsMembers = $CurrentSettings.AllowGuestsAsMembers
    }
    if ($null -ne $AllowGuestAccess) {
        $Body.AllowGuestAccess = $AllowGuestAccess
    }
    if ($null -ne $AllowGuestsAsMembers) {
        $Body.AllowGuestsAsMembers = $AllowGuestsAsMembers
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgMicrosoftTeams {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $AllowCalendarSharing
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/skypeteams"

    $Body = Get-O365OrgMicrosoftTeams -Headers $Headers

    # It seems every time you check https://admin.microsoft.com/#/Settings/Services/:/Settings/L1/SkypeTeams
    # and you enable just 1 or two settings you need to reapply everything! so i'll
    # leave it for now - as it needs more investigation
    # $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    # $Output
}
function Set-O365OrgModernAuthentication {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Headers
    Parameter description
 
    .PARAMETER EnableModernAuth
    Parameter description
 
    .PARAMETER SecureDefaults
    Parameter description
 
    .PARAMETER DisableModernAuth
    Parameter description
 
    .PARAMETER AllowBasicAuthActiveSync
    Parameter description
 
    .PARAMETER AllowBasicAuthImap
    Parameter description
 
    .PARAMETER AllowBasicAuthPop
    Parameter description
 
    .PARAMETER AllowBasicAuthWebServices
    Parameter description
 
    .PARAMETER AllowBasicAuthPowershell
    Parameter description
 
    .PARAMETER AllowBasicAuthAutodiscover
    Parameter description
 
    .PARAMETER AllowBasicAuthMapi
    Parameter description
 
    .PARAMETER AllowBasicAuthOfflineAddressBook
    Parameter description
 
    .PARAMETER AllowBasicAuthRpc
    Parameter description
 
    .PARAMETER AllowBasicAuthSmtp
    Parameter description
 
    .PARAMETER AllowOutlookClient
    Parameter description
 
    .EXAMPLE
    Set-O365OrgModernAuthentication -AllowBasicAuthImap $true -AllowBasicAuthPop $true -WhatIf
 
    .EXAMPLE
    Set-O365OrgModernAuthentication -AllowBasicAuthImap $false -AllowBasicAuthPop $false -Verbose -WhatIf
 
    .NOTES
    https://admin.microsoft.com/#/Settings/Services/:/Settings/L1/ModernAuthentication
 
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $EnableModernAuth, #: True
        [nullable[bool]] $SecureDefaults, #: False
        [nullable[bool]] $DisableModernAuth, #: False
        [nullable[bool]] $AllowBasicAuthActiveSync, #: True
        [nullable[bool]] $AllowBasicAuthImap, #: True
        [nullable[bool]] $AllowBasicAuthPop, #: True
        [nullable[bool]] $AllowBasicAuthWebServices, #: True
        [nullable[bool]] $AllowBasicAuthPowershell, #: True
        [nullable[bool]] $AllowBasicAuthAutodiscover, #: True
        [nullable[bool]] $AllowBasicAuthMapi, #: True
        [nullable[bool]] $AllowBasicAuthOfflineAddressBook , #: True
        [nullable[bool]] $AllowBasicAuthRpc, #: True
        [nullable[bool]] $AllowBasicAuthSmtp, #: True
        [nullable[bool]] $AllowOutlookClient                #:
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/modernAuth"
    $CurrentSettings = Get-O365OrgModernAuthentication -Headers $Headers
    if (-not $CurrentSettings) {
        Write-Warning -Message "Set-O365ModernAuthentication - Couldn't gather current settings. Skipping setting anything."
        return
    }
    $Body = [ordered] @{
        EnableModernAuth                 = $CurrentSettings.EnableModernAuth                 #: True
        SecureDefaults                   = $CurrentSettings.SecureDefaults                   #: False
        DisableModernAuth                = $CurrentSettings.DisableModernAuth                #: False
        AllowBasicAuthActiveSync         = $CurrentSettings.AllowBasicAuthActiveSync         #: True
        AllowBasicAuthImap               = $CurrentSettings.AllowBasicAuthImap               #: False
        AllowBasicAuthPop                = $CurrentSettings.AllowBasicAuthPop                #: False
        AllowBasicAuthWebServices        = $CurrentSettings.AllowBasicAuthWebServices        #: True
        AllowBasicAuthPowershell         = $CurrentSettings.AllowBasicAuthPowershell         #: True
        AllowBasicAuthAutodiscover       = $CurrentSettings.AllowBasicAuthAutodiscover       #: True
        AllowBasicAuthMapi               = $CurrentSettings.AllowBasicAuthMapi               #: True
        AllowBasicAuthOfflineAddressBook = $CurrentSettings.AllowBasicAuthOfflineAddressBook #: True
        AllowBasicAuthRpc                = $CurrentSettings.AllowBasicAuthRpc                #: True
        AllowBasicAuthSmtp               = $CurrentSettings.AllowBasicAuthSmtp               #: True
        AllowOutlookClient               = $CurrentSettings.AllowOutlookClient               #: True
    }
    if ($null -ne $SecureDefaults) {
        $Body.SecureDefaults = $SecureDefaults
    }
    if ($null -ne $EnableModernAuth) {
        $Body.EnableModernAuth = $EnableModernAuth
    }
    if ($null -ne $DisableModernAuth) {
        $Body.DisableModernAuth = $DisableModernAuth
    }
    if ($null -ne $AllowBasicAuthActiveSync) {
        $Body.AllowBasicAuthActiveSync = $AllowBasicAuthActiveSync
    }
    if ($null -ne $AllowBasicAuthImap) {
        $Body.AllowBasicAuthImap = $AllowBasicAuthImap
    }
    if ($null -ne $AllowBasicAuthPop) {
        $Body.AllowBasicAuthPop = $AllowBasicAuthPop
    }
    if ($null -ne $AllowBasicAuthWebServices) {
        $Body.AllowBasicAuthWebServices = $AllowBasicAuthWebServices
    }
    if ($null -ne $AllowBasicAuthPowershell) {
        $Body.AllowBasicAuthPowershell = $AllowBasicAuthPowershell
    }
    if ($null -ne $AllowBasicAuthAutodiscover) {
        $Body.AllowBasicAuthAutodiscover = $AllowBasicAuthAutodiscover
    }
    if ($null -ne $AllowBasicAuthMapi) {
        $Body.AllowBasicAuthMapi = $AllowBasicAuthMapi
    }
    if ($null -ne $AllowBasicAuthOfflineAddressBook) {
        $Body.AllowBasicAuthOfflineAddressBook = $AllowBasicAuthOfflineAddressBook
    }
    if ($null -ne $AllowBasicAuthRpc) {
        $Body.AllowBasicAuthRpc = $AllowBasicAuthRpc
    }
    if ($null -ne $AllowBasicAuthSmtp) {
        $Body.AllowBasicAuthSmtp = $AllowBasicAuthSmtp
    }
    if ($null -ne $AllowOutlookClient) {
        $Body.AllowOutlookClient = $AllowOutlookClient
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgMyAnalytics {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $EnableInsightsDashboard,
        [nullable[bool]] $EnableWeeklyDigest,
        [nullable[bool]] $EnableInsightsOutlookAddIn
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/myanalytics"

    $CurrentSettings = Get-O365OrgMyAnalytics -Headers $Headers
    if ($CurrentSettings) {
        $Body = @{
            value = @{
                IsDashboardOptedOut = $CurrentSettings.EnableInsightsDashboard
                IsEmailOptedOut     = $CurrentSettings.EnableWeeklyDigest
                IsAddInOptedOut     = $CurrentSettings.EnableInsightsOutlookAddIn
            }
        }
        if ($null -ne $EnableInsightsDashboard) {
            $Body.value.IsDashboardOptedOut = -not $EnableInsightsDashboard
        }
        if ($null -ne $EnableWeeklyDigest) {
            $Body.value.IsEmailOptedOut = -not $EnableWeeklyDigest
        }
        if ($null -ne $EnableInsightsOutlookAddIn) {
            $Body.value.IsAddInOptedOut = -not $EnableInsightsOutlookAddIn
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgNews {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $ContentOnNewTabEnabled,
        [nullable[bool]] $CompanyInformationAndIndustryEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/searchadminapi/news/options"

    $CurrentSettings = Get-O365OrgNews -Headers $Headers -NoTranslation
    if ($CurrentSettings) {
        $Body = [ordered] @{
            ServiceType = 'Bing'
            NewsOptions = $CurrentSettings.NewsOptions
        }
        if ($null -ne $ContentOnNewTabEnabled) {
            $Body.NewsOptions.EdgeNTPOptions.IsOfficeContentEnabled = $ContentOnNewTabEnabled
        }
        if ($null -ne $CompanyInformationAndIndustryEnabled) {
            $Body.NewsOptions.EdgeNTPOptions.IsShowCompanyAndIndustry = $CompanyInformationAndIndustryEnabled
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
        $Output
    }
}
function Set-O365OrgOfficeOnTheWeb {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $Enabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/officeonline"

    $Body = @{
        Enabled = $Enabled
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgOfficeProductivity {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [alias('ProductivityScoreOptedIn')][parameter(Mandatory)][bool] $Enabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/reports/productivityScoreCustomerOption"

    $Body = @{
        ProductivityScoreOptedIn = $Enabled
        OperationTime            = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgOrganizationInformation {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [string] $Name,
        [string] $StreetAddress,
        [string] $ApartmentOrSuite,
        [string] $City,
        [string] $State,
        [string] $PostalCode,
        #[string] $Country,
        #[string] $CountryCode,
        #[string] $PossibleStatesOrProvinces,
        [string] $PhoneNumber,
        [string] $TechnicalContactEmail
    )
    $Uri ="https://admin.microsoft.com/admin/api/Settings/company/profile"

    $CurrentSettings = Get-O365OrgOrganizationInformation -Headers $Headers
    if ($CurrentSettings) {
        $Body = @{
            Name                  = $CurrentSettings.Name                      ## : Evotec
            Address1              = $CurrentSettings.Address1                  ## :
            Address2              = $CurrentSettings.Address2                  ## :
            #Address3 = $CurrentSettings.Address3 ## :
            #Address4 = $CurrentSettings.Address4 ## :
            City                  = $CurrentSettings.City                      ## : KATOWICE
            State                 = $CurrentSettings.State                     ## : Śląskie
            PostalCode            = $CurrentSettings.PostalCode                ## : 40-
            Country               = $CurrentSettings.Country                   ## : Poland
            #CountryCode = $CurrentSettings.CountryCode ## : PL
            #PossibleStatesOrProvinces = $CurrentSettings.PossibleStatesOrProvinces ## :
            PhoneNumber           = $CurrentSettings.PhoneNumber               ## : +4
            TechnicalContactEmail = $CurrentSettings.TechnicalContactEmail     ## : p
            #DefaultDomain = $CurrentSettings.DefaultDomain ## :
            Language              = $CurrentSettings.Language                  ## : en
            #MSPPID = $CurrentSettings.MSPPID ## :
            #SupportUrl = $CurrentSettings.SupportUrl ## :
            #SupportEmail = $CurrentSettings.SupportEmail ## :
            #SupportPhone = $CurrentSettings.SupportPhone ## :
            SupportedLanguages    = $CurrentSettings.SupportedLanguages        ## : {@{ID=en; Name=English; Default=True; DefaultCulture=en-US; PluralFormRules=IsOne}, @{ID=pl; Name=polski; Default=False; DefaultCulture=pl-PL; PluralFormRules=IsOne,EndsInTwoThruFourNotTweleveThruFourteen}}
        }
        if ($PSBoundParameters.ContainsKey('Name')) {
            $Body.Name = $Name
        }
        if ($PSBoundParameters.ContainsKey('StreetAddress')) {
            $Body.Address1 = $StreetAddress
        }
        if ($PSBoundParameters.ContainsKey('ApartmentOrSuite')) {
            $Body.Address2 = $ApartmentOrSuite
        }
        if ($PSBoundParameters.ContainsKey('City')) {
            $Body.City = $City
        }
        if ($PSBoundParameters.ContainsKey('State')) {
            $Body.State = $State
        }
        if ($PSBoundParameters.ContainsKey('PostalCode')) {
            $Body.PostalCode = $PostalCode
        }
        #if ($PSBoundParameters.ContainsKey('Country')) {
        # $Body.Country = $Country
        #}
        #if ($PSBoundParameters.ContainsKey('CountryCode')) {
        # $Body.CountryCode = $CountryCode
        #}
        if ($PSBoundParameters.ContainsKey('PhoneNumber')) {
            $Body.PhoneNumber = $PhoneNumber
        }
        if ($PSBoundParameters.ContainsKey('TechnicalContactEmail')) {
            $Body.TechnicalContactEmail = $TechnicalContactEmail
        }

        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgPasswordExpirationPolicy {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter()][nullable[bool]] $PasswordNeverExpires,
        [Parameter()][nullable[int]] $DaysBeforePasswordExpires,
        [Parameter()][nullable[int]] $DaysBeforeUserNotified
    )
    $Uri ="https://admin.microsoft.com/admin/api/Settings/security/passwordpolicy"

    $CurrentSettings = Get-O365OrgPasswordExpirationPolicy -Headers $Headers -NoTranslation
    if ($CurrentSettings) {
        $Body = @{
            ValidityPeriod   = $CurrentSettings.ValidityPeriod   #: 90
            NotificationDays = $CurrentSettings.NotificationDays #: 14
            NeverExpire      = $CurrentSettings.NeverExpire      #: True
        }
        if ($null -ne $DaysBeforeUserNotified) {
            $Body.NotificationDays = $DaysBeforeUserNotified
        }
        if ($null -ne $DaysBeforePasswordExpires) {
            $Body.ValidityPeriod = $DaysBeforePasswordExpires
        }
        if ($null -ne $PasswordNeverExpires) {
            $Body.NeverExpire = $PasswordNeverExpires
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    }
}
function Set-O365OrgPlanner {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $AllowCalendarSharing
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/planner"

    $Body = @{
        allowCalendarSharing = $AllowCalendarSharing
        id                   = "1"
        isPlannerAllowed     = $true
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgPrivacyProfile {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter()][uri] $PrivacyUrl,
        [parameter()][string] $PrivacyContact
    )
    $Uri ="https://admin.microsoft.com/admin/api/Settings/security/privacypolicy"
    $Body = @{
        PrivacyStatement = $PrivacyUrl
        PrivacyContact   = $PrivacyContact
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgPrivilegedAccess {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $TenantLockBoxEnabled,
        [string] $AdminGroup
    )
    $Uri ="https://admin.microsoft.com/admin/api/Settings/security/tenantLockbox"

    $Body = @{
        EnabledTenantLockbox = $TenantLockBoxEnabled
        AdminGroup           = $AdminGroup
        Identity             = $null
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgProject {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $RoadmapEnabled,
        [nullable[bool]] $ProjectForTheWebEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/projectonline"

    $CurrentSettings = Get-O365OrgProject -Headers $Headers -NoTranslation
    if ($CurrentSettings) {
        $Body = @{
            IsRoadmapEnabled          = $CurrentSettings.IsRoadmapEnabled          #: True
            IsModProjEnabled          = $CurrentSettings.IsModProjEnabled          #: True
            RoadmapAvailabilityError  = $CurrentSettings.RoadmapAvailabilityError  #: 0
            ModProjAvailabilityStatus = $CurrentSettings.ModProjAvailabilityStatus #: 0
        }
        if ($null -ne $RoadmapEnabled) {
            $Body.IsRoadmapEnabled = $RoadmapEnabled
        }
        if ($null -ne $ProjectForTheWebEnabled) {
            $Body.IsModProjEnabled = $ProjectForTheWebEnabled
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgReleasePreferences {
    [cmdletbinding()]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][ValidateSet('FirstRelease', 'StagedRollout', 'None')] $ReleaseTrack
    )

    $Uri = 'https://admin.microsoft.com/admin/api/Settings/company/releasetrack'

    $Body = [ordered] @{
        ReleaseTrack = $ReleaseTrack
        ShowCompass  = $false
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}

function Set-O365OrgReports {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter()][nullable[bool]] $PrivacyEnabled,
        [Parameter()][nullable[bool]] $PowerBiEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/reports/config/SetTenantConfiguration"

    $Body = @{
        PrivacyEnabled = $PrivacyEnabled
        PowerBiEnabled = $PowerBiEnabled
    }
    Remove-EmptyValue -Hashtable $Body
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgScripts {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter()][string][ValidateSet('Disabled', 'Everyone', 'SpecificGroup')] $LetUsersAutomateTheirTasks,
        [Parameter()][string] $LetUsersAutomateTheirTasksGroup,
        [Parameter()][string] $LetUsersAutomateTheirTasksGroupID,
        [Parameter()][string][ValidateSet('Disabled', 'Everyone', 'SpecificGroup')] $LetUsersShareTheirScripts,
        [Parameter()][string] $LetUsersShareTheirScriptsGroup,
        [Parameter()][string] $LetUsersShareTheirScriptsGroupID,
        [Parameter()][string][ValidateSet('Disabled', 'Everyone', 'SpecificGroup')] $LetUsersRunScriptPowerAutomate,
        [Parameter()][string] $LetUsersRunScriptPowerAutomateGroup,
        [Parameter()][string] $LetUsersRunScriptPowerAutomateGroupID
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/officescripts"

    $Body = [ordered] @{}
    if ($LetUsersAutomateTheirTasks -eq 'Disabled') {
        # if the user wants to disable the LetUsersAutomateTheirTasks, then we need to disable all other options as well
        $Body.EnabledOption = 0
        $Body.ShareOption = 0
        $Body.UnattendedOption = 0
    } else {
        if ($LetUsersAutomateTheirTasks -or $LetUsersAutomateTheirTasksGroup -or $LetUsersAutomateTheirTasksGroupID) {
            # We check for the presence of option, but also if the user just provided a group name or ID we then assume the user wants specific group
            if ($LetUsersAutomateTheirTasks -eq 'SpecificGroup' -or $LetUsersAutomateTheirTasksGroup -or $LetUsersAutomateTheirTasksGroupID) {
                if ($LetUsersAutomateTheirTasksGroup) {
                    # we find the id of the group from the name
                    $Group = Get-O365Group -DisplayName $LetUsersAutomateTheirTasksGroup -Headers $Headers
                    if ($Group.Id) {
                        $Body.EnabledOption = 2
                        $Body.EnabledGroup = $Group.Id
                    } else {
                        Write-Warning -Message "Set-O365Scripts - LetUsersAutomateTheirTasksGroup couldn't be translated to ID. Skipping."
                        return
                    }
                } elseif ($LetUsersAutomateTheirTasksGroupID) {
                    # we use direct ID
                    $Body.EnabledOption = 2
                    $Body.EnabledGroup = $LetUsersAutomateTheirTasksGroupID
                } else {
                    Write-Warning -Message "Set-O365Scripts - LetUsersAutomateTheirTasksGroup/LetUsersAutomateTheirTasksGroupID not provided. Please provide group."
                    return
                }
            } elseif ($LetUsersAutomateTheirTasks -eq 'Everyone') {
                $Body.EnabledOption = 1
            } elseif ($LetUsersAutomateTheirTasks -eq 'Disabled') {
                $Body.EnabledOption = 0
            }
        }
        if ($LetUsersShareTheirScripts -or $LetUsersShareTheirScriptsGroup -or $LetUsersShareTheirScriptsGroupID) {
            # We check for the presence of option, but also if the user just provided a group name or ID we then assume the user wants specific group
            if ($LetUsersShareTheirScripts -eq 'SpecificGroup' -or $LetUsersShareTheirScriptsGroup -or $LetUsersShareTheirScriptsGroupID) {
                if ($LetUsersShareTheirScriptsGroup) {
                    # we find the id of the group from the name
                    $Group = Get-O365Group -DisplayName $LetUsersShareTheirScriptsGroup -Headers $Headers
                    if ($Group.Id) {
                        $Body.ShareOption = 2
                        $Body.ShareGroup = $Group.Id
                    } else {
                        Write-Warning -Message "Set-O365Scripts - LetUsersAutomateTheirTasksGroup couldn't be translated to ID. Skipping."
                        return
                    }
                } elseif ($LetUsersShareTheirScriptsGroupID) {
                    # we use direct ID
                    $Body.ShareOption = 2
                    $Body.ShareGroup = $LetUsersShareTheirScriptsGroupID
                } else {
                    Write-Warning -Message "Set-O365Scripts - LetUsersShareTheirScriptsGroup/LetUsersShareTheirScriptsGroupID not provided. Please provide group."
                    return
                }
            } elseif ($LetUsersShareTheirScripts -eq 'Everyone') {
                $Body.ShareOption = 1
            } elseif ($LetUsersShareTheirScripts -eq 'Disabled') {
                $Body.ShareOption = 0
            }
        }
        if ($LetUsersRunScriptPowerAutomate -or $LetUsersRunScriptPowerAutomateGroup -or $LetUsersRunScriptPowerAutomateGroupID) {
            # We check for the presence of option, but also if the user just provided a group name or ID we then assume the user wants specific group
            if ($LetUsersRunScriptPowerAutomate -eq 'SpecificGroup' -or $LetUsersRunScriptPowerAutomateGroup -or $LetUsersRunScriptPowerAutomateGroupID) {
                if ($LetUsersRunScriptPowerAutomateGroup) {
                    # we find the id of the group from the name
                    $Group = Get-O365Group -DisplayName $LetUsersRunScriptPowerAutomateGroup -Headers $Headers
                    if ($Group.Id) {
                        $Body.UnattendedOption = 2
                        $Body.UnattendedGroup = $Group.Id
                    } else {
                        Write-Warning -Message "Set-O365Scripts - LetUsersRunScriptPowerAutomateGroup couldn't be translated to ID. Skipping."
                        return
                    }
                } elseif ($LetUsersRunScriptPowerAutomateGroupID) {
                    # we use direct ID
                    $Body.UnattendedOption = 2
                    $Body.UnattendedGroup = $LetUsersRunScriptPowerAutomateGroupID
                } else {
                    Write-Warning -Message "Set-O365Scripts - LetUsersShareTheirScriptsGroup/LetUsersRunScriptPowerAutomateGroupID not provided. Please provide group."
                    return
                }
            } elseif ($LetUsersRunScriptPowerAutomateGroup -eq 'Everyone') {
                $Body.UnattendedOption = 1
            } elseif ($LetUsersRunScriptPowerAutomateGroup -eq 'Disabled') {
                $Body.UnattendedOption = 0
            }
        }
    }
    $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    $Output
}
function Set-O365OrgSharePoint {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][ValidateSet('OnlyPeopleInYourOrganization', 'ExistingGuestsOnly', 'NewAndExistingGuestsOnly', 'Anyone')][string] $CollaborationType
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/sitessharing"

    $ReverseTranslateCollaboration = @{
        'NewAndExistingGuestsOnly'     = 2
        'Anyone'                       = 16
        'ExistingGuestsOnly'           = 32
        'OnlyPeopleInYourOrganization' = 1
    }

    $Body = @{
        AllowSharing      = if ($CollaborationType -eq 'OnlyPeopleInYourOrganization') { $false } else { $true }
        CollaborationType = $ReverseTranslateCollaboration[$CollaborationType]
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgSharing {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [Parameter(Mandatory)][bool] $LetUsersAddNewGuests
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/security/guestUserPolicy"
    $Body = @{
        AllowGuestInvitations = $LetUsersAddNewGuests
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgSway {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $ExternalSharingEnabled,
        [nullable[bool]] $PeoplePickerSearchEnabled,
        [nullable[bool]] $FlickrEnabled,
        [nullable[bool]] $PickitEnabled,
        [nullable[bool]] $WikipediaEnabled,
        [nullable[bool]] $YouTubeEnabled

    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/Sway"

    $CurrentSettings = Get-O365OrgSway -Headers $Headers
    if ($CurrentSettings) {
        $Body = [ordered] @{
            ExternalSharingEnabled    = $CurrentSettings.ExternalSharingEnabled    # : True
            PeoplePickerSearchEnabled = $CurrentSettings.PeoplePickerSearchEnabled # : True
            FlickrEnabled             = $CurrentSettings.FlickrEnabled             # : True
            PickitEnabled             = $CurrentSettings.PickitEnabled             # : True
            WikipediaEnabled          = $CurrentSettings.WikipediaEnabled          # : True
            YouTubeEnabled            = $CurrentSettings.YouTubeEnabled            # : True
        }

        if ($null -ne $ExternalSharingEnabled) {
            $Body.ExternalSharingEnabled = $ExternalSharingEnabled
        }
        if ($null -ne $FlickrEnabled) {
            $Body.FlickrEnabled = $FlickrEnabled
        }
        if ($null -ne $PickitEnabled) {
            $Body.PickitEnabled = $PickitEnabled
        }
        if ($null -ne $WikipediaEnabled) {
            $Body.WikipediaEnabled = $WikipediaEnabled
        }
        if ($null -ne $YouTubeEnabled) {
            $Body.YouTubeEnabled = $YouTubeEnabled
        }
        if ($null -ne $PeoplePickerSearchEnabled) {
            $Body.PeoplePickerSearchEnabled = $PeoplePickerSearchEnabled
        }

        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    }
}
function Set-O365OrgTodo {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $ExternalJoinEnabled,
        [nullable[bool]] $PushNotificationEnabled,
        [nullable[bool]] $ExternalShareEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/services/apps/todo"

    $CurrentSettings = Get-O365OrgToDo -Headers $Headers
    if ($CurrentSettings) {
        $Body = @{
            IsExternalJoinEnabled     = $CurrentSettings.IsExternalJoinEnabled
            IsPushNotificationEnabled = $CurrentSettings.IsPushNotificationEnabled
            IsExternalShareEnabled    = $CurrentSettings.IsExternalShareEnabled
        }
        if ($null -ne $ExternalJoinEnabled) {
            $Body.IsExternalJoinEnabled = $ExternalJoinEnabled
        }
        if ($null -ne $PushNotificationEnabled) {
            $Body.IsPushNotificationEnabled = $PushNotificationEnabled
        }
        if ($null -ne $ExternalShareEnabled) {
            $Body.IsExternalShareEnabled = $ExternalShareEnabled
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
        $Output
    }
}
function Set-O365OrgUserConsentApps {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [parameter(Mandatory)][bool] $UserConsentToAppsEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/IntegratedApps"

    $Body = @{
        Enabled = $UserConsentToAppsEnabled
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}
function Set-O365OrgUserOwnedApps {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $LetUsersAccessOfficeStore,
        [nullable[bool]] $LetUsersStartTrials,
        [nullable[bool]] $LetUsersAutoClaimLicenses
    )

    if ($null -ne $LetUsersAccessOfficeStore) {
        $Uri ="https://admin.microsoft.com/admin/api/settings/apps/store"
        $Body = @{
            Enabled = $LetUsersAccessOfficeStore
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    }
    if ($null -ne $LetUsersStartTrials) {
        $TrialState = $LetUsersStartTrials.ToString().ToLower()
        $Uri ="https://admin.microsoft.com/admin/api/storesettings/iwpurchase/$TrialState"
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT #-Body $Body
    }
    if ($null -ne $LetUsersAutoClaimLicenses) {

        $Uri ="https://admin.microsoft.com/fd/m365licensing/v1/policies/autoclaim"

        $Body = @{
            policyValue = if ($LetUsersAutoClaimLicenses -eq $true) { 'Enabled' } else { 'Disabled' }
        }

        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
    }
}
function Set-O365OrgWhiteboard {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $WhiteboardEnabled,
        [ValidateSet('Neither', 'Required', 'Optional')]$DiagnosticData,
        [nullable[bool]] $OptionalConnectedExperiences,
        [nullable[bool]] $BoardSharingEnabled,
        [nullable[bool]] $OneDriveStorageEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/settings/apps/whiteboard"

    $CurrentSettings = Get-O365OrgWhiteboard -Headers $Headers -NoTranslation

    $Body = [ordered] @{
        IsEnabled                   = $CurrentSettings.IsEnabled                   # : True
        IsClaimEnabled              = $CurrentSettings.IsClaimEnabled              #: True
        IsSharePointDefault         = $CurrentSettings.IsSharePointDefault         #: False
        # This always seems to be 0, but i'll let it read it from Get-O365OrgWhiteboard
        NonTenantAccess             = $CurrentSettings.NonTenantAccess             #: 0
        TelemetryPolicy             = $CurrentSettings.TelemetryPolicy             #: 2
        AreConnectedServicesEnabled = $CurrentSettings.AreConnectedServicesEnabled #: True
    }
    if ($null -ne $WhiteboardEnabled) {
        $Body.IsEnabled = $WhiteboardEnabled
    }
    if ($DiagnosticData) {
        $ReverseTranslateTelemetry = @{
            'Neither'  = 0
            'Required' = 1
            'Optional' = 2
        }
        $Body.TelemetryPolicy = $ReverseTranslateTelemetry[$DiagnosticData]
    }
    if ($null -ne $OptionalConnectedExperiences) {
        $Body.AreConnectedServicesEnabled = $OptionalConnectedExperiences
    }
    if ($null -ne $BoardSharingEnabled) {
        $Body.IsClaimEnabled = $BoardSharingEnabled
    }
    if ($null -ne $OneDriveStorageEnabled) {
        $Body.IsSharePointDefault = $OneDriveStorageEnabled
    }
    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
}

function Set-O365PasswordReset {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[int]] $EnablementType                       , # = $CurrentSettings.enablementType #: 1
        [nullable[int]]$NumberOfAuthenticationMethodsRequired, # = $CurrentSettings.numberOfAuthenticationMethodsRequired #: 1
        [nullable[bool]] $EmailOptionEnabled                   , # = $CurrentSettings.emailOptionEnabled #: True
        [nullable[bool]] $MobilePhoneOptionEnabled             , # = $CurrentSettings.mobilePhoneOptionEnabled #: True
        [nullable[bool]] $OfficePhoneOptionEnabled             , # = $CurrentSettings.officePhoneOptionEnabled #: False
        [nullable[bool]] $SecurityQuestionsOptionEnabled       , # = $CurrentSettings.securityQuestionsOptionEnabled #: False
        [nullable[bool]] $MobileAppNotificationEnabled         , # = $CurrentSettings.mobileAppNotificationEnabled #: False
        [nullable[bool]] $MobileAppCodeEnabled                 , # = $CurrentSettings.mobileAppCodeEnabled #: True
        [nullable[int]]  $NumberOfQuestionsToRegister          , # = $CurrentSettings.numberOfQuestionsToRegister #: 5
        [nullable[int]] $NumberOfQuestionsToReset             , # = $CurrentSettings.numberOfQuestionsToReset #: 3
        [nullable[bool]] $RegistrationRequiredOnSignIn         , # = $CurrentSettings.registrationRequiredOnSignIn #: True
        [nullable[int]]$RegistrationReconfirmIntevalInDays   , # = $CurrentSettings.registrationReconfirmIntevalInDays #: 180
        [nullable[bool]] $SkipRegistrationAllowed              , # = $CurrentSettings.skipRegistrationAllowed #: True
        [nullable[int]] $SkipRegistrationMaxAllowedDays       , # = $CurrentSettings.skipRegistrationMaxAllowedDays #: 7
        [nullable[bool]] $CustomizeHelpdeskLink                , # = $CurrentSettings.customizeHelpdeskLink #: False
        [string] $CustomHelpdeskEmailOrUrl             , # = $CurrentSettings.customHelpdeskEmailOrUrl #:
        [nullable[bool]] $NotifyUsersOnPasswordReset           , # = $CurrentSettings.notifyUsersOnPasswordReset #: True
        [nullable[bool]] $NotifyOnAdminPasswordReset           , # = $CurrentSettings.notifyOnAdminPasswordReset #: True
        [string] $PasswordResetEnabledGroupIds         , # = $CurrentSettings.passwordResetEnabledGroupIds #: {b6cdb9c3-d660-4558-bcfd-82c14a986b56}
        [string] $PasswordResetEnabledGroupName        , # = $CurrentSettings.passwordResetEnabledGroupName #:
        # don't have details about those. needs investigations
        # $securityQuestions , # = $CurrentSettings.securityQuestions #: {}
        #$registrationConditionalAccessPolicies, # = $CurrentSettings.registrationConditionalAccessPolicies #: {}
        [nullable[bool]] $EmailOptionAllowed                   , # = $CurrentSettings.emailOptionAllowed #: True
        [nullable[bool]] $MobilePhoneOptionAllowed             , # = $CurrentSettings.mobilePhoneOptionAllowed #: True
        [nullable[bool]] $OfficePhoneOptionAllowed             , # = $CurrentSettings.officePhoneOptionAllowed #: True
        [nullable[bool]] $SecurityQuestionsOptionAllowed       , # = $CurrentSettings.securityQuestionsOptionAllowed #: True
        [nullable[bool]] $MobileAppNotificationOptionAllowed   , # = $CurrentSettings.mobileAppNotificationOptionAllowed #: True
        [nullable[bool]] $MobileAppCodeOptionAllowed            # = $CurrentSettings.mobileAppCodeOptionAllowed #: True
    )
    $Uri = "https://main.iam.ad.ext.azure.com/api/PasswordReset/PasswordResetPolicies"

    $CurrentSettings = Get-O365PasswordReset -Headers $Headers

    if ($CurrentSettings.objectId -ne 'default') {
        Write-Warning -Message "Set-O365PasswordReset - Getting current settings failed. Skipping changes."
        return
    }

    $Body = [ordered] @{
        objectId                              = $CurrentSettings.objectId #: default
        enablementType                        = $CurrentSettings.enablementType #: 1
        numberOfAuthenticationMethodsRequired = $CurrentSettings.numberOfAuthenticationMethodsRequired #: 1
        emailOptionEnabled                    = $CurrentSettings.emailOptionEnabled #: True
        mobilePhoneOptionEnabled              = $CurrentSettings.mobilePhoneOptionEnabled #: True
        officePhoneOptionEnabled              = $CurrentSettings.officePhoneOptionEnabled #: False
        securityQuestionsOptionEnabled        = $CurrentSettings.securityQuestionsOptionEnabled #: False
        mobileAppNotificationEnabled          = $CurrentSettings.mobileAppNotificationEnabled #: False
        mobileAppCodeEnabled                  = $CurrentSettings.mobileAppCodeEnabled #: True
        numberOfQuestionsToRegister           = $CurrentSettings.numberOfQuestionsToRegister #: 5
        numberOfQuestionsToReset              = $CurrentSettings.numberOfQuestionsToReset #: 3
        registrationRequiredOnSignIn          = $CurrentSettings.registrationRequiredOnSignIn #: True
        registrationReconfirmIntevalInDays    = $CurrentSettings.registrationReconfirmIntevalInDays #: 180
        skipRegistrationAllowed               = $CurrentSettings.skipRegistrationAllowed #: True
        skipRegistrationMaxAllowedDays        = $CurrentSettings.skipRegistrationMaxAllowedDays #: 7
        customizeHelpdeskLink                 = $CurrentSettings.customizeHelpdeskLink #: False
        customHelpdeskEmailOrUrl              = $CurrentSettings.customHelpdeskEmailOrUrl #:
        notifyUsersOnPasswordReset            = $CurrentSettings.notifyUsersOnPasswordReset #: True
        notifyOnAdminPasswordReset            = $CurrentSettings.notifyOnAdminPasswordReset #: True
        passwordResetEnabledGroupIds          = $CurrentSettings.passwordResetEnabledGroupIds #: {b6cdb9c3-d660-4558-bcfd-82c14a986b56}
        passwordResetEnabledGroupName         = $CurrentSettings.passwordResetEnabledGroupName #:
        securityQuestions                     = $CurrentSettings.securityQuestions #: {}
        registrationConditionalAccessPolicies = $CurrentSettings.registrationConditionalAccessPolicies #: {}
        emailOptionAllowed                    = $CurrentSettings.emailOptionAllowed #: True
        mobilePhoneOptionAllowed              = $CurrentSettings.mobilePhoneOptionAllowed #: True
        officePhoneOptionAllowed              = $CurrentSettings.officePhoneOptionAllowed #: True
        securityQuestionsOptionAllowed        = $CurrentSettings.securityQuestionsOptionAllowed #: True
        mobileAppNotificationOptionAllowed    = $CurrentSettings.mobileAppNotificationOptionAllowed #: True
        mobileAppCodeOptionAllowed            = $CurrentSettings.mobileAppCodeOptionAllowed #: True
    }

    if ($null -ne $EnablementType) {
        $Body.enablementType = $EnablementType
    }
    if ($null -ne $NumberOfAuthenticationMethodsRequired) {
        $Body.numberOfAuthenticationMethodsRequired = $NumberOfAuthenticationMethodsRequired
    }
    if ($null -ne $EmailOptionEnabled) {
        $Body.emailOptionEnabled = $EmailOptionEnabled
    }
    if ($null -ne $MobilePhoneOptionEnabled) {
        $Body.mobilePhoneOptionEnabled = $MobilePhoneOptionEnabled
    }
    if ($null -ne $OfficePhoneOptionEnabled) {
        $Body.officePhoneOptionEnabled = $OfficePhoneOptionEnabled
    }
    if ($null -ne $SecurityQuestionsOptionEnabled) {
        $Body.securityQuestionsOptionEnabled = $SecurityQuestionsOptionEnabled
    }
    if ($null -ne $MobileAppNotificationEnabled) {
        $Body.mobileAppNotificationEnabled = $MobileAppNotificationEnabled
    }
    if ($null -ne $MobileAppCodeEnabled) {
        $Body.mobileAppCodeEnabled = $MobileAppCodeEnabled
    }
    if ($null -ne $NumberOfQuestionsToRegister) {
        $Body.numberOfQuestionsToRegister = $NumberOfQuestionsToRegister
    }
    if ($null -ne $NumberOfQuestionsToReset) {
        $Body.numberOfQuestionsToReset = $NumberOfQuestionsToReset
    }
    if ($null -ne $RegistrationRequiredOnSignIn) {
        $Body.registrationRequiredOnSignIn = $RegistrationRequiredOnSignIn
    }
    if ($null -ne $RegistrationReconfirmIntevalInDays) {
        $Body.registrationReconfirmIntevalInDays = $RegistrationReconfirmIntevalInDays
    }
    if ($null -ne $SkipRegistrationAllowed) {
        $Body.skipRegistrationAllowed = $SkipRegistrationAllowed
    }
    if ($null -ne $SkipRegistrationMaxAllowedDays) {
        $Body.skipRegistrationMaxAllowedDays = $SkipRegistrationMaxAllowedDays
    }
    if ($CustomizeHelpdeskLink) {
        $Body.customizeHelpdeskLink = $CustomizeHelpdeskLink
    }
    if ($null -ne $CustomHelpdeskEmailOrUrl) {
        $Body.customHelpdeskEmailOrUrl = $CustomHelpdeskEmailOrUrl
    }
    if ($null -ne $NotifyUsersOnPasswordReset) {
        $Body.notifyUsersOnPasswordReset = $NotifyUsersOnPasswordReset
    }
    if ($null -ne $NotifyOnAdminPasswordReset) {
        $Body.notifyOnAdminPasswordReset = $NotifyOnAdminPasswordReset
    }
    if ($PasswordResetEnabledGroupName) {
        # We should find an easy way to find ID of a group and set it here
        $Group = Get-O365Group -DisplayName $PasswordResetEnabledGroupName
        if ($Group.id) {
            if ($Group -is [PSCustomObject]) {
                # Not implemented yet
                $Body.passwordResetEnabledGroupIds = @(
                    # Query for group id from group name $PasswordResetEnabledGroupName
                    $Group.id
                )
            } else {
                Write-Warning -Message "Set-O365PasswordReset - Group with name $PasswordResetEnabledGroupName not found or too many groups returned ($($Group.id -join ", "))"
                return
            }
        } else {
            Write-Warning -Message "Set-O365PasswordReset - Group with name $PasswordResetEnabledGroupName not found."
            return
        }
    } elseif ($PasswordResetEnabledGroupIds) {
        $Body.passwordResetEnabledGroupIds = @(
            $PasswordResetEnabledGroupIds
        )

        # This seems like an empty value - always
        $Body.passwordResetEnabledGroupName = ''
    }
    if ($null -ne $SecurityQuestions) {
        $Body.securityQuestions = $SecurityQuestions
    }
    if ($null -ne $RegistrationConditionalAccessPolicies) {
        $Body.registrationConditionalAccessPolicies = $RegistrationConditionalAccessPolicies
    }
    if ($null -ne $EmailOptionAllowed) {
        $Body.emailOptionAllowed = $EmailOptionAllowed
    }
    if ($null -ne $MobilePhoneOptionAllowed) {
        $Body.mobilePhoneOptionAllowed = $MobilePhoneOptionAllowed
    }
    if ($null -ne $OfficePhoneOptionAllowed) {
        $Body.officePhoneOptionAllowed = $OfficePhoneOptionAllowed
    }
    if ($null -ne $SecurityQuestionsOptionAllowed) {
        $Body.securityQuestionsOptionAllowed = $SecurityQuestionsOptionAllowed
    }
    if ($null -ne $mobileAppNotificationOptionAllowed) {
        $Body.mobileAppNotificationOptionAllowed = $mobileAppNotificationOptionAllowed
    }
    if ($null -ne $mobileAppCodeOptionAllowed) {
        $Body.mobileAppCodeOptionAllowed = $mobileAppCodeOptionAllowed
    }

    $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
}
function Set-O365PasswordResetIntegration {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [nullable[bool]] $PasswordWritebackSupported,
        [alias('AccountUnlockSupported')][nullable[bool]] $AllowUsersTounlockWithoutReset
    )
    $Uri ="https://main.iam.ad.ext.azure.com/api/PasswordReset/OnPremisesPasswordResetPolicies"

    # It seems you need to set this separatly for AccountUnlockEnabled to be picked up properly.
    # So we do it..
    if ($null -ne $PasswordWritebackSupported) {
        $Body = @{
            passwordWriteBackSupported = $passwordWriteBackSupported
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
    }
    if ($null -ne $AllowUsersTounlockWithoutReset) {
        $Body = @{
            accountUnlockEnabled = $AllowUsersTounlockWithoutReset
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
    }
}
function Set-O365SearchIntelligenceBingConfigurations {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [bool] $ServiceEnabled
    )
    $Uri ="https://admin.microsoft.com/admin/api/searchadminapi/configuration/update"

    if ($PSBoundParameters.ContainsKey('ServiceEnabled')) {

        $CurrentSettings = Get-O365SearchIntelligenceBingConfigurations -Headers $Headers
        if ($CurrentSettings) {
            $Body = @{
                IsServiceEnabledStateChanged = $true
                # GUI only allows a single change to all services at once
                <#
                ServiceEnabled = $CurrentSettings.ServiceEnabled #: False
                People = $CurrentSettings.People #: False
                Groups = $CurrentSettings.Groups #: False
                Documents = $CurrentSettings.Documents #: False
                Yammer = $CurrentSettings.Yammer #: False
                Teams = $CurrentSettings.Teams #: False
#>

                ServiceEnabled               = $ServiceEnabled               #: False
                People                       = $ServiceEnabled                       #: False
                Groups                       = $ServiceEnabled                       #: False
                Documents                    = $ServiceEnabled                    #: False
                Yammer                       = $ServiceEnabled                       #: False
                Teams                        = $ServiceEnabled                        #: False
                SaveTenantSettings           = $true           #: True
            }

            $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method POST -Body $Body
            $Output
        }
    }
}
function Set-O365SearchIntelligenceBingExtension {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [bool] $EnableExtension,
        [Array] $LimitGroupId,
        [Array] $LimitGroupName
    )
    $Uri ="https://admin.microsoft.com/fd/bfb/api/v3/office/switch/feature"

    if ($EnableExtension -eq $false) {
        $Body = @{
            Features = @(4, 7)
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method DELETE -Body $Body
    } elseif ($EnableExtension -eq $true -and ($LimitGroupID.Count -gt 0 -or $LimitGroupName.Count -gt 0)) {
        # We need to first disable extension and then enable it again
        Set-O365SearchIntelligenceBingExtension -EnableExtension $false -Headers $Headers
        Start-Sleep -Seconds 1
        [Array] $Groups = @(
            foreach ($Group in $LimitGroupID) {
                $GroupInformation = Get-O365Group -Id $Group -Headers $Headers
                if ($GroupInformation.id) {
                    $GroupInformation
                }
            }
            foreach ($Group in $LimitGroupName) {
                $GroupInformation = Get-O365Group -DisplayName $Group -Headers $Headers
                if ($GroupInformation.id) {
                    $GroupInformation
                }
            }
        )
        $Body = @{
            Features                    = @(4, 7 )
            BingDefaultsEnabledGroupIds = @(
                foreach ($Group in $Groups) {
                    $Group.id
                }
            )
            BingDefaultsEnabledGroups   = [ordered] @{}
            # @{b6cdb9c3-d660-4558-bcfd-82c14a986b56=All Users; 5f2910bc-d7a2-4529-bc66-c9d5181ab236=Graph}
        }
        foreach ($Group in $Groups) {
            $Body['BingDefaultsEnabledGroups'][$Group.id] = $Group.displayName
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
    } elseif ($EnableExtension -eq $true) {
        # We need to first disable extension and then enable it again
        Set-O365SearchIntelligenceBingExtension -EnableExtension $false -Headers $Headers
        Start-Sleep -Seconds 1
        $Body = @{
            Features = @(4)
        }
        $null = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
    }
}
function Set-O365SearchIntelligenceItemInsights {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [bool] $AllowItemInsights,
        [string] $DisableGroupName,
        [string] $DisableGroupID
    )
    $Uri ="https://admin.microsoft.com/fd/configgraphprivacy/ceb371f6-8745-4876-a040-69f2d10a9d1a/settings/ItemInsights"

    if ($PSBoundParameters.ContainsKey('AllowItemInsights')) {
        if ($DisableGroupID) {
            $GroupInformation = Get-O365Group -Id $DisableGroupID -Headers $Headers
        } elseif ($DisableGroupName) {
            $GroupInformation = Get-O365Group -DisplayName $DisableGroupName -Headers $Headers
        }
        if ($GroupInformation.id) {
            $DisabledForGroup = $GroupInformation.id
        } else {
            $DisabledForGroup = $null
        }
        $Body = @{
            isEnabledInOrganization = $AllowItemInsights
            disabledForGroup        = $DisabledForGroup
        }
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PATCH -Body $Body
        $Output
    }
}
function Set-O365SearchIntelligenceMeetingInsights {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('Authorization')][System.Collections.IDictionary] $Headers,
        [bool] $AllowMeetingInsights
    )
    $Uri ="https://admin.microsoft.com/fd/ssms/api/v1.0/'3srecs'/Collection('meetinginsights')/Settings(Path=':',LogicalId='MeetingInsightsToggle')"

    if ($PSBoundParameters.ContainsKey('AllowMeetingInsights')) {
        $Body = @{
            Payload = $AllowMeetingInsights.ToString().ToLower()
        }
        #"{`"Payload`":`"false`"}"
        $Output = Invoke-O365Admin -Uri $Uri -Headers $Headers -Method PUT -Body $Body
        $Output
    }
}



# Export functions and aliases as required
Export-ModuleMember -Function @('Connect-O365Admin', 'ConvertFrom-JSONWebToken', 'Get-O365AzureADConnect', 'Get-O365AzureADConnectPTA', 'Get-O365AzureADConnectSSO', 'Get-O365AzureADRoles', 'Get-O365AzureADRolesMember', 'Get-O365AzureConditionalAccess', 'Get-O365AzureConditionalAccessClassic', 'Get-O365AzureConditionalAccessLocation', 'Get-O365AzureConditionalAccessPolicy', 'Get-O365AzureConditionalAccessTerms', 'Get-O365AzureConditionalAccessVPN', 'Get-O365AzureEnterpriseAppsGroupConsent', 'Get-O365AzureEnterpriseAppsUserConsent', 'Get-O365AzureEnterpriseAppsUserSettings', 'Get-O365AzureEnterpriseAppsUserSettingsAdmin', 'Get-O365AzureEnterpriseAppsUserSettingsPromoted', 'Get-O365AzureExternalCollaborationFlows', 'Get-O365AzureExternalCollaborationSettings', 'Get-O365AzureExternalIdentitiesEmail', 'Get-O365AzureExternalIdentitiesPolicies', 'Get-O365AzureFeatureConfiguration', 'Get-O365AzureFeaturePortal', 'Get-O365AzureGroupExpiration', 'Get-O365AzureGroupGeneral', 'Get-O365AzureGroupM365', 'Get-O365AzureGroupNamingPolicy', 'Get-O365AzureGroupSecurity', 'Get-O365AzureGroupSelfService', 'Get-O365AzureLicenses', 'Get-O365AzureMultiFactorAuthentication', 'Get-O365AzureProperties', 'Get-O365AzurePropertiesSecurity', 'Get-O365AzureTenantSKU', 'Get-O365AzureUserSettings', 'Get-O365BillingAccounts', 'Get-O365BillingInvoices', 'Get-O365BillingLicenseAutoClaim', 'Get-O365BillingLicenseRequests', 'Get-O365BillingNotifications', 'Get-O365BillingNotificationsList', 'Get-O365BillingPaymentMethods', 'Get-O365BillingProfile', 'Get-O365BillingSubscriptions', 'Get-O365ConsiergeAll', 'Get-O365DirectorySync', 'Get-O365DirectorySyncErrors', 'Get-O365DirectorySyncManagement', 'Get-O365Domain', 'Get-O365DomainDependencies', 'Get-O365DomainHealth', 'Get-O365DomainRecords', 'Get-O365DomainTroubleshooting', 'Get-O365Group', 'Get-O365GroupAdministrativeUnit', 'Get-O365GroupLicenses', 'Get-O365GroupMember', 'Get-O365OrgAzureSpeechServices', 'Get-O365OrgBingDataCollection', 'Get-O365OrgBookings', 'Get-O365OrgBriefingEmail', 'Get-O365OrgCalendarSharing', 'Get-O365OrgCommunicationToUsers', 'Get-O365OrgCortana', 'Get-O365OrgCustomerLockbox', 'Get-O365OrgCustomThemes', 'Get-O365OrgDataLocation', 'Get-O365OrgDynamics365ConnectionGraph', 'Get-O365OrgDynamics365CustomerVoice', 'Get-O365OrgDynamics365SalesInsights', 'Get-O365OrgForms', 'Get-O365OrgGraphDataConnect', 'Get-O365OrgHelpdeskInformation', 'Get-O365OrgInstallationOptions', 'Get-O365OrgM365Groups', 'Get-O365OrgMicrosoftTeams', 'Get-O365OrgModernAuthentication', 'Get-O365OrgMyAnalytics', 'Get-O365OrgNews', 'Get-O365OrgOfficeOnTheWeb', 'Get-O365OrgOfficeProductivity', 'Get-O365OrgOrganizationInformation', 'Get-O365OrgPasswordExpirationPolicy', 'Get-O365OrgPlanner', 'Get-O365OrgPrivacyProfile', 'Get-O365OrgPrivilegedAccess', 'Get-O365OrgProject', 'Get-O365OrgReleasePreferences', 'Get-O365OrgReports', 'Get-O365OrgScripts', 'Get-O365OrgSharePoint', 'Get-O365OrgSharing', 'Get-O365OrgSway', 'Get-O365OrgToDo', 'Get-O365OrgUserConsentApps', 'Get-O365OrgUserOwnedApps', 'Get-O365OrgWhiteboard', 'Get-O365PartnerRelationship', 'Get-O365PasswordReset', 'Get-O365PasswordResetIntegration', 'Get-O365SearchIntelligenceBingConfigurations', 'Get-O365SearchIntelligenceBingExtension', 'Get-O365SearchIntelligenceItemInsights', 'Get-O365SearchIntelligenceMeetingInsights', 'Get-O365ServicePrincipal', 'Get-O365TenantID', 'Get-O365User', 'Invoke-O365Admin', 'New-O365License', 'Set-O365AzureEnterpriseAppsGroupConsent', 'Set-O365AzureEnterpriseAppsUserConsent', 'Set-O365AzureEnterpriseAppsUserSettings', 'Set-O365AzureEnterpriseAppsUserSettingsAdmin', 'Set-O365AzureExternalCollaborationSettings', 'Set-O365AzureGroupExpiration', 'Set-O365AzureGroupM365', 'Set-O365AzureGroupNamingPolicy', 'Set-O365AzureGroupSecurity', 'Set-O365AzureGroupSelfService', 'Set-O365AzureMultiFactorAuthentication', 'Set-O365AzureProperties', 'Set-O365AzurePropertiesSecurity', 'Set-O365AzureUserSettings', 'Set-O365BillingLicenseAutoClaim', 'Set-O365BillingNotifications', 'Set-O365GroupLicenses', 'Set-O365OrgAzureSpeechServices', 'Set-O365OrgBingDataCollection', 'Set-O365OrgBookings', 'Set-O365OrgBriefingEmail', 'Set-O365OrgCalendarSharing', 'Set-O365OrgCommunicationToUsers', 'Set-O365OrgCortana', 'Set-O365OrgCustomerLockbox', 'Set-O365OrgDynamics365ConnectionGraph', 'Set-O365OrgDynamics365CustomerVoice', 'Set-O365OrgDynamics365SalesInsights', 'Set-O365OrgForms', 'Set-O365OrgGraphDataConnect', 'Set-O365OrgHelpdeskInformation', 'Set-O365OrgInstallationOptions', 'Set-O365OrgM365Groups', 'Set-O365OrgMicrosoftTeams', 'Set-O365OrgModernAuthentication', 'Set-O365OrgMyAnalytics', 'Set-O365OrgNews', 'Set-O365OrgOfficeOnTheWeb', 'Set-O365OrgOfficeProductivity', 'Set-O365OrgOrganizationInformation', 'Set-O365OrgPasswordExpirationPolicy', 'Set-O365OrgPlanner', 'Set-O365OrgPrivacyProfile', 'Set-O365OrgPrivilegedAccess', 'Set-O365OrgProject', 'Set-O365OrgReleasePreferences', 'Set-O365OrgReports', 'Set-O365OrgScripts', 'Set-O365OrgSharePoint', 'Set-O365OrgSharing', 'Set-O365OrgSway', 'Set-O365OrgTodo', 'Set-O365OrgUserConsentApps', 'Set-O365OrgUserOwnedApps', 'Set-O365OrgWhiteboard', 'Set-O365PasswordReset', 'Set-O365PasswordResetIntegration', 'Set-O365SearchIntelligenceBingConfigurations', 'Set-O365SearchIntelligenceBingExtension', 'Set-O365SearchIntelligenceItemInsights', 'Set-O365SearchIntelligenceMeetingInsights') -Alias @('Get-O365OrgCompanyInformation')
# SIG # Begin signature block
# MIIdWQYJKoZIhvcNAQcCoIIdSjCCHUYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUUnoc5/bfKHNWpiNMa5vIhNic
# ANmgghhnMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAw
# MDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4G
# A1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4d
# yG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnH
# bQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1L
# sVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpK
# jIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756Wwog
# L0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNV
# HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIl
# ssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgw
# MqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMu
# Y3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk
# LXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIl
# xHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie3
# 8+dSn5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTz
# ZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0y
# OIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4Tr
# weOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUwMIIEGKADAgEC
# AhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEw
# MjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7
# RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p
# 0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj
# 6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grk
# V7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHy
# DxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMB
# AAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT
# BgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG
# GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh
# Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB
# gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgG
# CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1
# DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEL
# BQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q
# 3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/
# kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dc
# IFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6
# dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT
# +hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHk
# Bdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMT
# G0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0z
# MTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0
# IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5
# fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb
# 6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU
# 46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mI
# UF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfx
# FwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAd
# BgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS
# y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkw
# RzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLp
# UYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQd
# aq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC
# 4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+
# tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6H
# USHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIv
# IjayS6JKldj1po5SMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkq
# hkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoX
# DTIzMDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tp
# ZTERMA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlz
# IEVWT1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqD
# Bqlnr3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfF
# jVye3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub
# +3tii0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf
# 3tZZzO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6
# Ea41zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQAB
# o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O
# BBYEFBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
# DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny
# bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw
# QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl
# cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp
# Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1s
# z4lsLARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsR
# XPHUF/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHI
# NrTCvPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8
# Rj9yG4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6
# o6ESJre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNp
# ezQug9ufqExx6lHYDjGCBFwwggRYAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD
# VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv
# BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC
# EATV3B9I6snYUgC6zZqbKqcwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFF6lLI9B1+DkKMXUiokj
# Akys8/wtMA0GCSqGSIb3DQEBAQUABIIBALu1R+dL6guOED0/O/jo/XIvZnIIBSXK
# j5ATaoOu5yUikB+lCasqk1jiVRS+Ec3jQ+Ju744WxmC8Ix5mTL05YeDUCUCK0IzO
# wTBpeqx9+rupnneVGRbGo/bwLxWyfs2Tt2/wYiOFNh8YdBW0VV7zelzjtnCzQ8bA
# yDepxe+onPLOnbDtEW9PrLfy1Rsq8AB/fqQEs6ToHfduR9rIdZKu2/PM6nDknJzd
# geT52CBrou7C7qPHJQzNb36P9/J9cZlyZNhq+vO9mT648x8VmddZjoUXvbNkwcJ3
# JGgY3qgQ52mfHauDgV3m66Rm3sVcop8+Nwa0ptZ8oPBGR6uXpnKexHihggIwMIIC
# LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G
# A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ
# DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMTAxMTE4MzM1OFowLwYJKoZI
# hvcNAQkEMSIEIANyGGv3sswDQipuCTTJRKNj92jumj2ZVp19CRpFKt/6MA0GCSqG
# SIb3DQEBAQUABIIBAGvwuaZpQMgYhAxSYWkKePpSQpgoR5eUnBYsXppHeE1C1swy
# uYvTOkQ2tJsbjAKHEnfOJK06xqqvUCz3G8PIs5U9PAEfDphUb97bE5ovHPbqVZFT
# yo+09DZ41azHnLsa/Bde4128ZgiRGMj9FaGEG9nDKm8VVnhBZrJZNWyLfeKUYUyw
# ptgp5FtkPH2pHacIyPgavDha0LXsz7xIr1v4m2s7VY8jlBcGsQrTLPjOOg4ZxZ0C
# FcDeC176S/snJt/LFTejTgUwXIP7i/13DUSfbeYXY2FV9F9ghAH+L4yLPQfmFrzH
# bkJOpgKNXYK1tezytu8MbrnD/QBtOtbKnL2isuY=
# SIG # End signature block