MSGraphStuff.psm1

function Expand-MgAdditionalProperties {
    <#
    .SYNOPSIS
    Function for expanding 'AdditionalProperties' hash property to the main object aka flattens object.
 
    .DESCRIPTION
    Function for expanding 'AdditionalProperties' hash property to the main object aka flattens object.
    By default it is returned by commands like Get-MgDirectoryObjectById, Get-MgGroupMember etc.
 
    .PARAMETER inputObject
    Object returned by Mg* command that contains 'AdditionalProperties' property.
 
    .EXAMPLE
    Get-MgGroupMember -GroupId 90daa3a7-7fed-4fa7-a979-db74bcd7cbd0 | Expand-MgAdditionalProperties
 
    .EXAMPLE
    Get-MgDirectoryObjectById -ids 34568a12-8862-45cf-afef-9582cd9871c6 | Expand-MgAdditionalProperties
    #>


    [CmdletBinding()]
    param(
        [parameter(ValueFromPipeline)]
        [object] $inputObject
    )

    process {
        if ($inputObject.AdditionalProperties -and $inputObject.AdditionalProperties.gettype().name -eq 'Dictionary`2') {
            $inputObject.AdditionalProperties.GetEnumerator() | % {
                $item = $_
                Write-Verbose "Adding property '$($item.key)' to the pipeline object"
                $inputObject | Add-Member -MemberType NoteProperty -Name $item.key -Value $item.value

                if ($item.key -eq "@odata.type") {
                    Write-Verbose "Adding extra property 'ObjectType' to the pipeline object"
                    $inputObject | Add-Member -MemberType NoteProperty -Name 'ObjectType' -Value ($item.value -replace [regex]::Escape("#microsoft.graph."))
                }
            }

            $inputObject | Select-Object -Property * -ExcludeProperty AdditionalProperties
        } else {
            Write-Verbose "There is no 'AdditionalProperties' property"
            $inputObject
        }
    }
}

function Get-MgGroupSettings {
    <#
    .SYNOPSIS
    Function for getting group settings.
    Official Get-MgGroup -Property Settings doesn't return anything for some reason.
 
    .DESCRIPTION
    Function for getting group settings.
    Official Get-MgGroup -Property Settings doesn't return anything for some reason.
 
    .PARAMETER groupId
    Group ID.
 
    .EXAMPLE
    Get-MGGroupSettings -groupId 01c19ec3-e1bb-44f3-ab36-86071b745375
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $groupId
    )

    Invoke-MgGraphRequest -Uri "v1.0/groups/$groupId/settings" -OutputType PSObject | select -exp value | select *, @{n = 'ValuesAsObject'; e = {
            # return settings values as proper hashtable
            $hash = @{}
            $_.Values | % { $hash.($_.name) = $_.value }
            $hash
        }
    } #-ExcludeProperty Values
}

function Get-MgSkuAssignment {
    <#
    .SYNOPSIS
    Function returns users with selected Sku license.
 
    .DESCRIPTION
    Function returns users with selected Sku license.
 
    .PARAMETER sku
    SkuId or SkuPartNumber of the O365 license Sku.
    If not provided, all users and their Skus will be outputted.
 
    SkuId/SkuPartNumber can be found via: Get-MgSubscribedSku -All
 
    .PARAMETER assignmentType
    Limit what kind of license assignment the user needs to have.
 
    Possible values are: 'direct', 'inherited'
 
    By default users with both types are displayed.
 
    .EXAMPLE
    Get-MgSkuAssignment -sku "f8a1db68-be16-40ed-86d5-cb42ce701560"
 
    Get all users with selected sku (defined by id).
 
    .EXAMPLE
    Get-MgSkuAssignment -sku "POWER_BI_PRO"
 
    Get all users with selected sku.
 
    .EXAMPLE
    Get-MgSkuAssignment
 
    Get all users and their skus.
 
    .EXAMPLE
    Get-MgSkuAssignment -assignmentType direct
 
    Get all users which have some sku assigned directly.
 
    .EXAMPLE
    Get-MgSkuAssignment -sku "POWER_BI_PRO" -assignmentType inherited
 
    Get all users with selected sku if it is inherited.
    #>


    [CmdletBinding()]
    param (
        [ArgumentCompleter( {
                param ($Command, $Parameter, $WordToComplete, $CommandAst, $FakeBoundParams)

                Get-MgSubscribedSku -Property SkuPartNumber, SkuId -All | ? SkuPartNumber -Like "*$WordToComplete*" | select -ExpandProperty SkuPartNumber
            })]
        [string] $sku,

        [ValidateSet('direct', 'inherited')]
        [string[]] $assignmentType = ('direct', 'inherited'),

        [string[]] $userProperty = ('id', 'userprincipalname', 'assignedLicenses', 'LicenseAssignmentStates')
    )

    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): The context is invalid. Please login using Connect-MgGraph."
    }

    # add mandatory property
    if ($userProperty -notcontains 'assignedLicenses') { $userProperty += 'assignedLicenses' }
    if ($userProperty -notcontains 'LicenseAssignmentStates') { $userProperty += 'LicenseAssignmentStates' }

    $param = @{
        Select = $userProperty
        All    = $true
    }

    if ($sku) {
        $skuId = Get-MgSubscribedSku -Property SkuPartNumber, SkuId -All | ? { $_.SkuId -eq $sku -or $_.SkuPartNumber -eq $sku } | select -ExpandProperty SkuId
        if (!$skuId) {
            throw "Sku with id $skuId doesn't exist"
        }
        $param.Filter = "assignedLicenses/any(u:u/skuId eq $skuId)"
    }

    if ($assignmentType.count -eq 2) {
        # has some license
        $whereFilter = { $_.assignedLicenses }
    } elseif ($assignmentType -contains 'direct') {
        # direct assignment
        if ($sku) {
            $whereFilter = { $_.assignedLicenses -and ($_.LicenseAssignmentStates | ? { $_.SkuId -eq $skuId -and $_.AssignedByGroup -eq $null }) }
        } else {
            $whereFilter = { $_.assignedLicenses -and ($_.LicenseAssignmentStates.AssignedByGroup -eq $null).count -ge 1 }
        }
    } else {
        # inherited assignment
        if ($sku) {
            $whereFilter = { $_.assignedLicenses -and ($_.LicenseAssignmentStates | ? { $_.SkuId -eq $skuId -and $_.AssignedByGroup -ne $null }) }
        } else {
            $whereFilter = { $_.assignedLicenses -and $_.LicenseAssignmentStates.AssignedByGroup -ne $null }
        }
    }

    Get-MgUser @param | select $userProperty | ? $whereFilter
}

function Get-MgSkuAssignmentError {
    <#
    .SYNOPSIS
    Function returns users that have problems with licenses assignment.
 
    .DESCRIPTION
    Function returns users that have problems with licenses assignment.
    #>


    if (!(Get-Command Get-MgContext -ErrorAction silentlycontinue) -or !(Get-MgContext)) {
        throw "$($MyInvocation.MyCommand): The context is invalid. Please login using Connect-MgGraph."
    }

    $userWithLicenseProblem = Get-MgUser -Property UserPrincipalName, Id, LicenseAssignmentStates -All | ? { $_.LicenseAssignmentStates.state -eq 'error' }

    foreach ($user in $userWithLicenseProblem) {
        $errorLicense = $user.LicenseAssignmentStates | ? State -EQ "Error"

        foreach ($license in $errorLicense) {
            [PSCustomObject]@{
                UserPrincipalName   = $user.UserPrincipalName
                UserId              = $user.Id
                LicError            = $license.Error
                AssignedByGroup     = $license.AssignedByGroup
                AssignedByGroupName = (if ($license.AssignedByGroup) { (Get-MgGroup -GroupId $license.AssignedByGroup -Property DisplayName).DisplayName })
                LastUpdatedDateTime = $license.LastUpdatedDateTime
                SkuId               = $license.SkuId
                SkuName             = (Get-MgSubscribedSku -Property SkuPartNumber, SkuId -All | ? { $_.SkuId -eq $license.SkuId } | select -ExpandProperty SkuPartNumber)
            }
        }
    }
}

function Get-MgUser2 {
    [CmdletBinding(DefaultParameterSetName = 'List1', PositionalBinding = $false)]
    param(
        [Parameter(ParameterSetName = 'Get1', Mandatory = $true)]
        [string]
        ${UserId},

        [Parameter(ParameterSetName = 'GetViaIdentity1', Mandatory = $true, ValueFromPipeline = $true)]
        [Microsoft.Graph.PowerShell.Models.IUsersIdentity]
        ${InputObject},

        [Alias('Expand')]
        [string[]]
        ${ExpandProperty},

        [Alias('Select')]
        [string[]]
        ${Property},

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

        [Parameter(ParameterSetName = 'List1')]
        [string]
        ${Search},

        [Parameter(ParameterSetName = 'List1')]
        [int]
        ${Skip},

        [Parameter(ParameterSetName = 'List1')]
        [Alias('OrderBy')]
        [string[]]
        ${Sort},

        [Parameter(ParameterSetName = 'List1')]
        [Alias('Limit')]
        [int]
        ${Top},

        [Parameter(ParameterSetName = 'List1')]
        [string]
        ${ConsistencyLevel},

        [switch]
        ${Break},

        [ValidateNotNull()]
        [Microsoft.Graph.PowerShell.Runtime.SendAsyncStep[]]
        ${HttpPipelineAppend},

        [ValidateNotNull()]
        [Microsoft.Graph.PowerShell.Runtime.SendAsyncStep[]]
        ${HttpPipelinePrepend},

        [uri]
        ${Proxy},

        [ValidateNotNull()]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${ProxyCredential},

        [switch]
        ${ProxyUseDefaultCredentials},

        [Parameter(ParameterSetName = 'List1')]
        [int]
        ${PageSize},

        [Parameter(ParameterSetName = 'List1')]
        [switch]
        ${All},

        [Parameter(ParameterSetName = 'List1')]
        [Alias('CV')]
        [string]
        ${CountVariable})

    begin {
        try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-MgUser', [System.Management.Automation.CommandTypes]::Function)
            if ($PSBoundParameters['Property'] -eq '*') {
                # DeviceEnrollmentLimit property causes error: "No OData route exists that match template ~/entityset/key with http verb GET for request /StatelessOnboardingService/users..."
                $skipProperty = @("DeviceEnrollmentLimit")
                if ((Get-MgContext).account -ne $PSBoundParameters['UserId']) {
                    # MailboxSettings property if called upon different user than yourself causes error: "Access is denied. Check credentials and try again."
                    # https://stackoverflow.com/questions/54767695/error-access-denied-on-mailboxsettings-for-users
                    Write-Verbose "Skipping property MailboxSettings. Can be used only upon yourself"
                    $skipProperty += "MailboxSettings"
                } else {
                    Write-Warning "If error 'Resource could not be discovered' occurs. It means account '$($PSBoundParameters['UserId'])' doesn't have any mailbox and it is caused by retrieving property 'MailboxSettings'."
                }
                Write-Verbose "'*' property was selected. Retrieving all available properties (except: $($skipProperty -join ', '))"
                $PSBoundParameters['Property'] = Get-MgUser -Top 1 | Get-Member -MemberType NoteProperty, Property | select -ExpandProperty Name | ? { $_ -notin $skipProperty }
            }
            $scriptCmd = { & $wrappedCmd @PSBoundParameters }


            $steppablePipeline = $scriptCmd.GetSteppablePipeline()
            $steppablePipeline.Begin($PSCmdlet)
        } catch {
            throw
        }
    }

    process {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
    <#
 
.ForwardHelpTargetName Get-MgUser
.ForwardHelpCategory Function
 
#>


}

function Invoke-GraphAPIRequest {
    <#
    .SYNOPSIS
    Function for creating request against Microsoft Graph API.
 
    .DESCRIPTION
    Function for creating request against Microsoft Graph API.
 
    It supports paging and throttling.
 
    .PARAMETER uri
    Request URI.
 
    https://graph.microsoft.com/v1.0/me/
    https://graph.microsoft.com/v1.0/devices
    https://graph.microsoft.com/v1.0/users
    https://graph.microsoft.com/v1.0/groups
    https://graph.microsoft.com/beta/servicePrincipals?&$expand=appRoleAssignedTo
    https://graph.microsoft.com/beta/servicePrincipals?$select=id,appId,servicePrincipalType,displayName
    https://graph.microsoft.com/beta/servicePrincipals?$filter=(servicePrincipalType%20eq%20%27ManagedIdentity%27)
    https://graph.microsoft.com/beta/servicePrincipals?$filter=contains(serialNumber,'$encoded')
    https://graph.microsoft.com/v1.0/deviceManagement/deviceCompliancePolicySettingStateSummaries/1234/deviceComplianceSettingStates?`$filter=NOT(state eq 'compliant')
    https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$select=id&`$filter=complianceState eq 'compliant'
    https://graph.microsoft.com/beta/users?`$select=id,userPrincipalName,displayName,mail,otherMails,proxyAddresses&`$filter=proxyAddresses/any(c:c eq 'smtp:$technicalNotificationMail') or otherMails/any(c:c eq 'smtp:$technicalNotificationMail')
 
    .PARAMETER credential
    Credentials used for creating authentication header for request.
 
    .PARAMETER header
    Authentication header for request.
 
    .PARAMETER method
    Default is GET.
 
    .PARAMETER waitTime
    Number of seconds before new try in case of 'Too Many Requests' error.
 
    Default is 5 seconds.
 
    .EXAMPLE
    $header = New-GraphAPIAuthHeader -credential $intuneCredential
    $aadDevice = Invoke-GraphAPIRequest -Uri "https://graph.microsoft.com/v1.0/devices" -header $header | Get-MSGraphAllPages
 
    .EXAMPLE
    $aadDevice = Invoke-GraphAPIRequest -Uri "https://graph.microsoft.com/v1.0/devices" -credential $intuneCredential | Get-MSGraphAllPages
 
    .NOTES
    https://configmgrblog.com/2017/12/05/so-what-can-we-do-with-microsoft-intune-via-microsoft-graph-api/
    #>


    [CmdletBinding()]
    [Alias("Invoke-MgRequest")]
    param (
        [Parameter(Mandatory = $true)]
        [string] $uri,

        [Parameter(Mandatory = $true, ParameterSetName = "credential")]
        [System.Management.Automation.PSCredential] $credential,

        [Parameter(Mandatory = $true, ParameterSetName = "header")]
        $header,

        [ValidateSet('GET', 'POST', 'DELETE', 'UPDATE')]
        [string] $method = "GET",

        [ValidateRange(1, 999)]
        [int] $waitTime = 5
    )

    Write-Verbose "uri $uri"

    if ($credential) {
        $header = New-GraphAPIAuthHeader -credential $credential
    }

    try {
        $response = Invoke-RestMethod -Uri $uri -Headers $header -Method $method -ErrorAction Stop
    } catch {
        switch ($_) {
            { $_ -like "*(429) Too Many Requests*" } {
                Write-Warning "(429) Too Many Requests. Waiting $waitTime seconds to avoid further throttling and try again"
                Start-Sleep $waitTime
                Invoke-GraphAPIRequest -uri $uri -header $header -method $method
            }
            { $_ -like "*(400) Bad Request*" } { throw "(400) Bad Request. There has to be some syntax/logic mistake in this request ($uri)" }
            { $_ -like "*(401) Unauthorized*" } { throw "(401) Unauthorized Request (new auth header has to be created?)" }
            { $_ -like "*Forbidden*" } { throw "Forbidden access. Use account with correct API permissions for this request ($uri)" }
            default { throw $_ }
        }
    }

    if ($response.Value) {
        $response.Value
    } else {
        $response
    }

    # understand if top parameter is used in the URI
    try {
        $prevErrorActionPreference = $ErrorActionPreference
        $ErrorActionPreference = "Stop"
        $topValue = ([regex]"top=(\d+)").Matches($uri).captures.groups[1].value
    } catch {
        Write-Verbose "uri ($uri) doesn't contain TOP"
    } finally {
        $ErrorActionPreference = $prevErrorActionPreference
    }

    if (!$topValue -or ($topValue -and $topValue -gt 100)) {
        # there can be more results to return, check that
        # need to loop the requests because only 100 results are returned each time
        $nextLink = $response.'@odata.nextLink'
        while ($nextLink) {
            Write-Verbose "Next uri $nextLink"
            try {
                $response = Invoke-RestMethod -Uri $NextLink -Headers $header -Method $method -ErrorAction Stop
            } catch {
                switch ($_) {
                    { $_ -like "*(429) Too Many Requests*" } {
                        Write-Warning "(429) Too Many Requests. Waiting $waitTime seconds to avoid further throttling and try again"
                        Start-Sleep $waitTime
                        Invoke-GraphAPIRequest -uri $NextLink -header $header -method $method
                    }
                    { $_ -like "*(400) Bad Request*" } { throw "(400) Bad Request. There has to be some syntax/logic mistake in this request ($uri)" }
                    { $_ -like "*(401) Unauthorized*" } { throw "(401) Unauthorized Request (new auth header has to be created?)" }
                    { $_ -like "*Forbidden*" } { throw "Forbidden access. Use account with correct API permissions for this request ($uri)" }
                    default { throw $_ }
                }
            }

            if ($response.Value) {
                $response.Value
            } else {
                $response
            }

            $nextLink = $response.'@odata.nextLink'
        }
    } else {
        # to avoid 'Too Many Requests' error when working with Graph API (/auditLogs/signIns) and using top parameter
        Write-Verbose "There is no need to check if more results can be returned. I.e. if parameter 'top' is used in the URI it is lower than 100 (so all results will be returned in the first request anyway)"
    }
}

function New-GraphAPIAuthHeader {
    <#
    .SYNOPSIS
    Function for generating header that can be used for authentication of Graph API requests.
 
    .DESCRIPTION
    Function for generating header that can be used for authentication of Graph API requests.
    Credentials can be given or existing AzureAD session can be reused to obtain auth. header.
 
    .PARAMETER credential
    Credentials for Graph API authentication (AppID + AppSecret) that will be used to obtain auth. header.
 
    .PARAMETER reuseExistingAzureADSession
    Switch for using existing AzureAD session (created via Connect-AcAccount) to obtain auth. header.
 
    .PARAMETER TenantDomainName
    Name of your Azure tenant.
 
    .PARAMETER showDialogType
    Modify behavior of auth. dialog window.
 
    Possible values are: auto, always, never.
 
    Default is 'never'.
 
    .PARAMETER tokenLifeTime
    Token lifetime in minutes.
    Will be saved into the header 'ExpiresOn' key and can be used for expiration detection (need to create new token).
    By default it is random number between 60 and 90 minutes (https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens#access-token-lifetime) but can be changed in tenant policy.
 
    Default is 60.
 
    .PARAMETER useADAL
    Switch for using ADAL for auth. token creation.
    Can solve problem with 'forbidden' errors when default token creation method is used, but can be used only under user accounts and will be deprecated soon!
 
    .EXAMPLE
    $cred = Get-Credential
    $header = New-GraphAPIAuthHeader -credential $cred
    $URI = 'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/'
    $managedDevices = (Invoke-RestMethod -Headers $header -Uri $URI -Method Get).value
 
    .EXAMPLE
    (there has to be existing AzureAD session already (made via Connect-AzAccount))
    $header = New-GraphAPIAuthHeader -reuseExistingAzureADSession
    $URI = 'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/'
    $managedDevices = (Invoke-RestMethod -Headers $header -Uri $URI -Method Get).value
 
    .EXAMPLE
    (there has to be existing AzureAD session already (made via Connect-AzureAD))
    $header = New-GraphAPIAuthHeader -reuseExistingAzureADSession -useADAL
    $URI = 'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/'
    $managedDevices = (Invoke-RestMethod -Headers $header -Uri $URI -Method Get).value
 
    Use ADAL for auth. token creation. Can help if default method leads to 'forbidden' errors when token is used.
 
    .NOTES
    https://adamtheautomator.com/powershell-graph-api/#AppIdSecret
    https://thesleepyadmins.com/2020/10/24/connecting-to-microsoft-graphapi-using-powershell/
    https://github.com/microsoftgraph/powershell-intune-samples
    https://tech.nicolonsky.ch/explaining-microsoft-graph-access-token-acquisition/
    https://gist.github.com/psignoret/9d73b00b377002456b24fcb808265c23
    #>


    [CmdletBinding()]
    [Alias("New-IntuneAuthHeader", "Get-IntuneAuthHeader", "New-MgAuthHeader")]
    param (
        [Parameter(ParameterSetName = "authenticate")]
        [System.Management.Automation.PSCredential] $credential,

        [Parameter(ParameterSetName = "reuseSession")]
        [switch] $reuseExistingAzureADSession,

        [ValidateNotNullOrEmpty()]
        [Alias("tenantId")]
        $tenantDomainName = $_tenantDomain,

        [ValidateSet('auto', 'always', 'never')]
        [string] $showDialogType = 'never',

        [Parameter(ParameterSetName = "reuseSession")]
        [switch] $useADAL,

        [int] $tokenLifeTime = 60
    )

    if (!$credential -and !$reuseExistingAzureADSession) {
        $credential = (Get-Credential -Message "Enter AppID as UserName and AppSecret as Password")
    }
    if (!$credential -and !$reuseExistingAzureADSession) { throw "Credentials for creating Graph API authentication header is missing" }

    if (!$tenantDomainName -and !$reuseExistingAzureADSession) { throw "TenantDomainName is missing" }

    Write-Verbose "Getting token"

    if ($reuseExistingAzureADSession) {
        # get auth. token using the existing session created by the Az.Accounts PowerShell module
        try {
            # test if connection already exists
            $aZconnection = Get-AzAccessToken -ResourceTypeName MSGraph -ea Stop
            if (!$aZconnection) { throw "no existing connection" }
        } catch {
            throw "There is no active session to AzureAD. Omit reuseExistingAzureADSession parameter or call this function after Connect-AzAccount."
        }

        try {
            $ErrorActionPreference = "Stop"

            if ($useADAL) {
                # https://github.com/microsoftgraph/powershell-intune-samples/blob/master/ManagedDevices/Win10_PrimaryUser_Set.ps1
                $context = [Microsoft.Open.Azure.AD.CommonLibrary.AzureRmProfileProvider]::Instance.Profile.Context
                $upn = $context.account.id
                Write-Verbose "Connecting using $upn"
                $tenant = (New-Object "System.Net.Mail.MailAddress" -ArgumentList $upn).Host

                Write-Verbose "Checking for AzureAD module..."
                $AadModule = Get-Module -Name "AzureAD" -ListAvailable

                if ($AadModule -eq $null) {
                    throw "AzureAD Powershell module not installed"
                }

                # Getting path to ActiveDirectory Assemblies
                # If the module count is greater than 1 find the latest version
                if ($AadModule.count -gt 1) {
                    $Latest_Version = ($AadModule | select version | Sort-Object)[-1]

                    $aadModule = $AadModule | ? { $_.version -eq $Latest_Version.version }

                    # Checking if there are multiple versions of the same module found
                    if ($AadModule.count -gt 1) {
                        $aadModule = $AadModule | select -Unique
                    }

                    $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
                    $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
                } else {
                    $adal = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
                    $adalforms = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
                }

                [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
                [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
                $clientId = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
                $redirectUri = "urn:ietf:wg:oauth:2.0:oob"
                $resourceAppIdURI = "https://graph.microsoft.com"
                $authority = "https://login.microsoftonline.com/$Tenant"

                $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

                # https://msdn.microsoft.com/en-us/library/azure/microsoft.identitymodel.clients.activedirectory.promptbehavior.aspx
                # Change the prompt behaviour to force credentials each time: Auto, Always, Never, RefreshSession
                $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList $showDialogType

                $userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($upn, "OptionalDisplayableId")

                $authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $clientId, $redirectUri, $platformParameters, $userId).Result

                # If the accesstoken is valid then create the authentication header
                if ($authResult.AccessToken) {
                    # Creating header for Authorization token
                    $authHeader = @{
                        'Authorization' = "Bearer " + $authResult.AccessToken
                        'ExpiresOn'     = $authResult.ExpiresOn
                    }

                    return $authHeader
                } else {
                    throw "Authorization Access Token is null, please re-run authentication..."
                }
            } else {
                # don't use ADAL

                if ($aZconnection) {
                    # use AZ connection
                    $token = Get-AzAccessToken -ResourceTypeName MSGraph -ErrorAction Stop

                    $authHeader = @{
                        ExpiresOn     = $token.ExpiresOn
                        Authorization = $token.token
                    }

                    return $authHeader
                } else {
                    # use AzureAD connection

                    # tento zpusob nekdy nefugnuje (dostavam forbidden)
                    $context = [Microsoft.Open.Azure.AD.CommonLibrary.AzureRmProfileProvider]::Instance.Profile.Context
                    $authenticationFactory = [Microsoft.Open.Azure.AD.CommonLibrary.AzureSession]::AuthenticationFactory
                    $msGraphEndpointResourceId = "MsGraphEndpointResourceId"
                    $msGraphEndpoint = $context.Environment.Endpoints[$msGraphEndpointResourceId]
                    $auth = $authenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Open.Azure.AD.CommonLibrary.ShowDialog]::$showDialogType, $null, $msGraphEndpointResourceId)

                    $token = $auth.AuthorizeRequest($msGraphEndpointResourceId)

                    $authHeader = @{
                        ExpiresOn     = (Get-Date).AddMinutes($tokenLifeTime - 10) # shorter by 10 minutes just for sure
                        Authorization = $token
                    }

                    return $authHeader
                }
            }
        } catch {
            throw "Unable to obtain auth. token:`n`n$($_.exception.message)`n`n$($_.invocationInfo.PositionMessage)`n`nTry change the showDialogType parameter?"
        }
    } else {
        # authenticate to obtain the token
        $body = @{
            Grant_Type    = "client_credentials"
            Scope         = "https://graph.microsoft.com/.default"
            Client_Id     = $credential.username
            Client_Secret = $credential.GetNetworkCredential().password
        }

        Write-Verbose "Setting TLS 1.2"
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

        Write-Verbose "Connecting to $tenantDomainName"
        $connectGraph = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantDomainName/oauth2/v2.0/token" -Method POST -Body $body

        $token = $connectGraph.access_token

        if ($token) {
            $authHeader = @{
                ExpiresOn     = (Get-Date).AddMinutes($tokenLifeTime - 10) # shorter by 10 minutes just for sure
                Authorization = "Bearer $($token)"
            }

            return $authHeader
        } else {
            throw "Unable to obtain token"
        }
    }
}

function Remove-MgUserMemberOfDirectoryRole {
    <#
    .SYNOPSIS
    Function for removing given user from given Directory role.
 
    .DESCRIPTION
    Function for removing given user from given Directory role.
 
    .PARAMETER userId
    ID of the user.
 
    Can be retrieved using Get-MgUser.
 
    .PARAMETER roleId
    ID of the Directory role.
 
    Can be retrieved using Get-MgUserMemberOf.
 
    .EXAMPLE
    $aadUser = Get-MgUser -Filter "userPrincipalName eq '$UPN'"
 
    Get-MgUserMemberOf -UserId $aadUser.id -All | ? { $_.AdditionalProperties.'@odata.type' -eq "#microsoft.graph.directoryRole" } | % {
        Remove-MgUserMemberOfDirectoryRole -userId $aadUser.id -roleId $_.id
    }
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $userId,
        [Parameter(Mandatory = $true)]
        [string] $roleId
    )

    # Use this endpoint when using the role Id
    $uri = "https://graph.microsoft.com/v1.0/directoryRoles/$roleId/members/$userId/`$ref"

    # Use this endpoint when using the role template ID
    # $uri = "https://graph.microsoft.com/v1.0/directoryRoles/roleTemplateId=$roleTemplateId/members/$userId/`$ref"

    $params = @{
        Headers = (New-GraphAPIAuthHeader -reuseExistingAzureADSession -ea Stop)
        Method  = "Delete"
        Uri     = $uri
    }

    Write-Verbose "Invoking DELETE method against '$uri'"
    Invoke-RestMethod @params
}

Export-ModuleMember -function Expand-MgAdditionalProperties, Get-MgGroupSettings, Get-MgSkuAssignment, Get-MgSkuAssignmentError, Get-MgUser2, Invoke-GraphAPIRequest, New-GraphAPIAuthHeader, Remove-MgUserMemberOfDirectoryRole

Export-ModuleMember -alias Get-IntuneAuthHeader, Invoke-MgRequest, New-IntuneAuthHeader, New-MgAuthHeader