MEMPSToolkit.psm1

#TODO: Use "mandatory" parameters

#region Authentication

# Save a Service Principal secret in an encrypted file
function Export-AppLoginSecret {
    param (
        [Parameter(Mandatory = $true)]
        [string]$clientId,
        [Parameter(Mandatory = $true)]
        [string]$tenant,
        #String or SecureString
        [Parameter(Mandatory = $true)]
        $secretValue,
        $path = "",
        $asDefault = $true
    )

    # Default filename and location
    if ($path -eq "") {
        if ($asDefault) {
            $path = $env:APPDATA + "\MEMPSToolkit_default_login.xml"
        }
        else {
            $path = $env:APPDATA + "\" + $tenant + "_" + $clientId + ".xml"
        }
    }

    if ($null -eq $secretValue) {
        return "Please specify a secret to store"
    }

    # Encrypt secret
    if ($secretValue.GetType().Name -eq "String") {
        $secretValue = ConvertTo-SecureString -String $secretValue -AsPlainText -Force
    }
    elseif ($secretValue.GetType().Name -ne "SecureString") {
        return "Please specify a secret as SecureString or String"
    }

    if (-not (Test-Path -Path $path)) {
        @(
            $clientId,
            $tenant,
            $secretValue
        ) | Export-Clixml -Path $path
    }
    else {
        return ($path + " exists. Will not overwrite")
    }
    Write-Output ("Saved to " + $path)
}

# Load Service Principal secret from an encrypted file and authenticate
function Get-AppLoginFromSavedSecret {
    param (
        $path = $env:APPDATA + "\MEMPSToolkit_default_login.xml",
        [switch]$returnRawToken = $false
    )

    if (-not (test-path -Path $path)) {
        throw "File not found"
    }

    $data = Import-Clixml -Path $path

    $clientId = $data[0]
    $tenant = $data[1]
    $secretValue = [System.Net.NetworkCredential]::new('', $data[2]).Password

    Get-AppLoginToken -tenant $tenant -clientId $clientId -secretValue $secretValue -returnRawToken:$returnRawToken
} 

# Authenticate non-interactively against a service principal / app registration with app permissions.
# Can be used headless, no libraries needed. Recommended.
function Get-AppLoginToken {
    param (
        [Parameter(Mandatory = $true)]
        $tenant,
        [Parameter(Mandatory = $true)]
        $clientId,
        [Parameter(Mandatory = $true)]
        $secretValue,
        [switch]$returnRawToken = $false,
        [int]$version = 2
    )

    # Which version of the auth. endpoint to use?
    if ($version -eq 2) {
        $LoginRequestParams = @{
            Method = 'POST'
            Uri    = "https://login.microsoftonline.com/" + $tenant + "/oauth2/v2.0/token"
            Body   = @{ 
                grant_type    = "client_credentials"; 
                scope         = "https://graph.microsoft.com/.default"; #v2.0 needs a scope
                client_id     = $clientId; 
                client_secret = $secretValue 
            }
        }
    }
    else {
        $LoginRequestParams = @{
            Method = 'POST'
            Uri    = "https://login.microsoftonline.com/" + $tenant + "/oauth2/token?api-version=1.0"
            Body   = @{ 
                grant_type    = "client_credentials"; 
                resource      = "https://graph.microsoft.com"; #v1.0 needs a resource
                client_id     = $clientId; 
                client_secret = $secretValue 
            }
        }
    }
    try {
        $result = Invoke-RestMethod @LoginRequestParams
    }
    catch {
        #Write-Output ("StatusCode:" + $_.Exception.Response.StatusCode.value__ )
        #Write-Output ("StatusDescription:" + $_.Exception.Response.StatusDescription)
        #Write-Output ("Message: " + $_.Exception.Message)
        #Write-Output ("Inner Error: " + $_.ErrorDetails.Message)
        Write-Error $_
        throw "Login with MS Graph API failed. See Error Log."
    }

    # If you want to use the token otherwise (e.g. Connect-MgGraph)
    if ($returnRawToken) {
        return $result.access_token
    }
    else {
        if ($version -eq 2) {
            return @{
                'Content-Type'  = 'application/json'
                'Authorization' = "Bearer " + $result.access_token
                #'ExpiresOn' = $result.expires_on # Not available in v2.0 endpoint
            }    
        }
        else {
            return @{
                'Content-Type'  = 'application/json'
                'Authorization' = "Bearer " + $result.access_token
                'ExpiresOn'     = $result.expires_on
            }
        }
    }
}

# Can be used in Azure Automation Runbooks to authenticate using a runbooks's stored credentials
function Get-AzAutomationCredLoginToken {
    param (
        $resource = "https://graph.microsoft.com",
        [Parameter(Mandatory = $true)]
        $tenant,
        $automationCredName = "RunbookStoredCred"
    )

    $cred = Get-AutomationPSCredential -Name $automationCredName
    $clientId = $cred.UserName
    $secrectValue = [System.Net.NetworkCredential]::new('', $cred.Password).Password

    return (Get-AppLoginToken -resource $resource -tenant $tenant -clientId $clientId -secretValue $secrectValue)
}

# Store a current token as default token in an encrypted file.
function Export-AppLoginToken {
    param (
        [Parameter(Mandatory = $true)]
        $authToken,
        $path = $env:APPDATA + "\MEMPSToolkit_default_token.xml",
        [bool]$overwrite = $true
    )

    if ($null -eq $authToken) {
        throw "Please provide an AuthToken object."
    }

    $encToken = ConvertTo-SecureString -String $authToken.Authorization -AsPlainText -Force

    if (Test-Path -Path $path) {
        if ($overwrite) {
            Remove-Item -Path $path           
        }
        else {
            return "File " + $path + " exists. Will not overwrite."
        }
    }

    $encToken | Export-Clixml -Path $path
}

# Load the default token from an encrypted file
function Import-AppLoginToken {
    param(
        $path = $env:APPDATA + "\MEMPSToolkit_default_token.xml"
    )
    if (-not (Test-Path -Path $path)) {
        # Well, nothing there.
        return $null
    } 

    $encToken = Import-Clixml -Path $path

    $token = @{
        "Content-Type"  = "application/json"
        "Authorization" = [System.Net.NetworkCredential]::new('', $encToken).Password
    }

    return $token
}

function Remove-AppLoginToken {
    param(
        $path = $env:APPDATA + "\MEMPSToolkit_default_token.xml"
    )

    if (Test-Path -Path $path) {
        Remove-Item -Path $path
    }
    else {
        return "File " + $path + " does not exists."
    }

}

# Interactively sign in using "device login flow". No libraries or GUI needed.
# Adapted from https://github.com/microsoftgraph/powershell-intune-samples
function Get-DeviceLoginToken {

    <#
    .SYNOPSIS
    This function is used to authenticate with the Graph API REST interface
    .DESCRIPTION
    The function authenticates with the Graph API Interface with the tenant name given using the OAUTH DeviceLogin flow.
    .EXAMPLE
    Get-DeviceLoginToken
    Authenticates you with the Graph API interface
    .NOTES
    NAME: Get-DeviceLoginToken
    #>

    
    [cmdletbinding()]
    
    param (
        [Parameter(Mandatory = $true)]
        $clientID,
        [Parameter(Mandatory = $true)]
        $tenant,
        $resource = "https://graph.microsoft.com",
        $scope = "https://graph.microsoft.com/.default https://graph.microsoft.com/Policy.Read.All"
    )

    $DeviceCodeRequestParams = @{
        Method = 'POST'
        Uri    = "https://login.microsoftonline.com/$tenant/oauth2/devicecode"
        Body   = @{
            client_id = $clientId
            resource  = $resource
        }
    }

    $DeviceCodeRequest = Invoke-RestMethod @DeviceCodeRequestParams
    Write-Host $DeviceCodeRequest.message -ForegroundColor Yellow
    Write-Host "Hit Enter when done signing in"
    Read-Host | Out-Null

    $TokenRequestParams = @{
        Method = 'POST'
        Uri    = "https://login.microsoftonline.com/$tenant/oauth2/token"
        Body   = @{
            grant_type = "urn:ietf:params:oauth:grant-type:device_code"
            code       = $DeviceCodeRequest.device_code
            client_id  = $ClientId
            scope      = $scope
        }
    }
    $TokenRequest = Invoke-RestMethod @TokenRequestParams

    $authHeader = @{
        'Content-Type'  = 'application/json'
        'Authorization' = "Bearer " + $TokenRequest.access_token
        'ExpiresOn'     = $TokenRequest.expires_on
    }

    return $authHeader
   
}

# Convert/Load a raw bearer token into an authHeader token.
function New-AppLoginToken {
    param(
        [Parameter(Mandatory = $true)]
        [string]$rawToken
    )

    $authHeader = @{
        'Content-Type'  = 'application/json'
        'Authorization' = "Bearer " + $rawToken
    }

    return $authHeader
}

#endregion Authentication

#region Basic API and File interaction

# Standardize Graph API calls / Trigger a REST-Call against MS Graph API and return the result
function Invoke-GraphRestRequest {
    param (
        $method = "GET",
        $prefix = "https://graph.microsoft.com/v1.0/",
        $resource = "users",
        $body = $null,
        $authToken = $null,
        $onlyValues = $true,
        $writeToFile = $false,
        $outFile = "MSGraphOutput.json"
    )
    
    if ($null -eq $authToken) {
        $authToken = Import-AppLoginToken
        if ($null -eq $authToken) {
            "Please provide an authentication token. You can use Get-DeviceLoginToken to acquire one."
        }
    }

    if ($prefix -eq "v1.0") {
        $prefix = "https://graph.microsoft.com/v1.0/"
    }
    elseif ($prefix -eq "beta") {
        $prefix = "https://graph.microsoft.com/beta/"
    }

    try {
        if ($writeToFile) {
            # TODO: Handle paging
            $result = Invoke-RestMethod -Uri ($prefix + $resource) -Headers $authToken -Method $method -Body $body -ContentType "application/json" -OutFile $outfile
        }
        else {
            $result = Invoke-RestMethod -Uri ($prefix + $resource) -Headers $authToken -Method $method -Body $body -ContentType "application/json"

            # Handle Paging
            $newresult = $result
            while ($newresult.PSObject.Properties.Name -contains "@odata.nextLink") {
                # actively ignore other parameters
                $newresult = Invoke-RestMethod -Uri ($newresult."@odata.nextLink") -Headers $authToken -Method $method -ContentType "application/json"
                $result.value += $newresult.value
            }
        }
    }
    catch {
        Write-Output ("StatusCode:" + $_.Exception.Response.StatusCode.value__ ) 
        Write-Output ("StatusDescription:" + $_.Exception.Response.StatusDescription)
        Write-Output ("Message: " + $_.Exception.Message)
        Write-Output ("Inner Error: " + $_.ErrorDetails.Message)
        Write-Error $_
        throw "Executing Graph Rest Call against " + $prefix + $resource + " failed. See Error Log."
    }

    if ($onlyValues) {
        return $result.Value
    }
    else {
        return $result
    }

}

# Write a JSON file from a Policy / group description object
function Export-PolicyObjects {
    param (
        [Parameter(Mandatory = $true)]
        [array]$policies
    )

    $policies | ForEach-Object {
        $name = $_.displayName -replace "[$([RegEx]::Escape([string][IO.Path]::GetInvalidFileNameChars()))]+", "_"
        if (-not (Test-Path ($name + ".json"))) {
            $_ | ConvertTo-Json -Depth 6 > ($name + ".json")
        }
        else {
            "Will not overwrite " + ($name + ".json") + ". Skipping."
        }
     
    }

}

# Load a Policy / group description object from a JSON file
function Import-PolicyObject {
    param (
        [Parameter(Mandatory = $true)]
        $filename
    )

    $JSON_Convert = Get-Content -Raw -Path $filename | ConvertFrom-Json -Depth 6

    return $JSON_Convert
}

#endregion

#region AAD group management

function Get-AADGroups {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "groups"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource $resource -authToken $authToken
}

function Add-AADGroupFromObject {
    param(
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        $groupObject = $null
    )

    if ($null -eq $groupObject) {
        return "Please provide a AAD group description object containing at least displayName, mailNickname, securityEnabled, mailEnabled properties" 
    }

    Add-AADGroup -mailNickName $groupObject.mailNickname -displayName $groupObject.displayName -securityEnabled $groupObject.securityEnabled -mailEnabled $groupObject.mailEnabled
}
function Add-AADGroup {
    param(
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        $displayName,
        [Parameter(Mandatory = $true)]
        $mailNickName,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $securityEnabled = "true",
        $mailEnabled = "false"
    )

    $resource = "groups"

    $groupDescription = @{
        displayName     = $displayName
        mailNickname    = $mailNickName
        securityEnabled = $securityEnabled
        mailEnabled     = $mailEnabled
    }

    $JSON = $groupDescription | ConvertTo-Json -Depth 6

    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource $resource -authToken $authToken -body $JSON -onlyValues $false
}

function Get-AADGroupById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        $groupId = ""
    )

    $resource = "groups"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $groupId) -authToken $authToken -onlyValues $false
}

function Get-AADGroupByName {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        $groupName = $null
    )

    $resource = "groups"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "?`$filter=displayName eq `'" + $groupName + "`'") -authToken $authToken -onlyValues $true
}

function Add-AADGroupFromFile {
    param(
        $importFile = "group.json",
        $authToken = $null
    )

    if (-not (Test-Path -Path $importFile)) {
        return     'Please provide a group description file as JSON, like:
{
    "mailNickname": "MEMTestGroup",
    "displayName": "MEMTestGroup",
    "mailEnabled": "false",
    "securityEnabled": "true"
}'

    }

    $groupData = Get-Content -Raw -Path $importFile | ConvertFrom-Json -Depth 6

    $group = Get-AADGroupByName -authToken $authToken -groupName $groupData.displayName

    if ($null -eq $group) {
        Add-AADGroup -authToken $authToken -displayName $groupData.displayName -mailNickName $groupData.mailNickname -securityEnabled $groupData.securityEnabled -mailEnabled $groupData.mailEnabled
    }
    else {
        "Group " + $groupData.displayName + " exists. Will skip. "
    }

}

function Update-AADGroupById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        $groupId = "",
        $valuePairs = @{}
    )

    $resource = "groups/$groupId"

    $body = $valuePairs | ConvertTo-Json

    Invoke-GraphRestRequest -method "PATCH" -prefix $prefix -resource $resource -authToken $authToken -onlyValues $false -body $body
}

function Get-AADGroupMembers {
    param (
        [Parameter(Mandatory = $true)]
        $groupID,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "groups"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $groupID + "/members") -authToken $authToken -onlyValues $true
}

function Add-AADGroupMember {
    param(
        [Parameter(Mandatory = $true)]
        $groupID,
        [Parameter(Mandatory = $true)]
        $userID,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "groups"

    $body = @"
{
    "@odata.id": "https://graph.microsoft.com/v1.0/directoryObjects/$userID"
}
"@


    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource ($resource + "/" + $groupID + "/members/`$ref") -body $body -authToken $authToken -onlyValues $false
}

function Remove-AADGroupMember {
    param(
        [Parameter(Mandatory = $true)]
        $groupID,
        [Parameter(Mandatory = $true)]
        $userID,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "groups"

    Invoke-GraphRestRequest -method "DELETE" -prefix $prefix -resource ($resource + "/" + $groupID + "/members/" + $userID + "/`$ref") -authToken $authToken -onlyValues $false
}


#endregion

#region devices
function Get-AADDevices {
    param (
        [string]$deviceId,
        $authToken = $null,
        [string]$prefix = "https://graph.microsoft.com/V1.0/"
    )
    
    $resource = "devices"

    if ($deviceId) {
        $resource = $resource + "?`$filter=deviceId eq '" + $deviceId + "`'"
    }

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource $resource -authToken $authToken -onlyValues $true
}

# Be aware - Only work with "delegated" permissions
function Disable-AADDevice {
    param (
        [Parameter(Mandatory = $true)]
        [string] $ObjectId,
        $authToken = $null,
        [string]$prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "devices/$ObjectId"

    $body = @{"accountEnabled" = $false } | ConvertTo-Json

    Invoke-GraphRestRequest -method "PATCH" -prefix $prefix -resource $resource -authToken $authToken -body $body -onlyValues $false

}

function Remove-AADDevice {
    param (
        [Parameter(Mandatory = $true)]
        [string] $ObjectId,
        $authToken = $null,
        [string]$prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "devices/$ObjectId"

    Invoke-GraphRestRequest -method "DELETE" -prefix $prefix -resource $resource -authToken $authToken -onlyValues $false

}

#endregion

#region AuthMethods

function Get-AADUserAuthMethods {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/methods") -authToken $authToken -onlyValues $true
    
}

function Get-AADUserMSAuthenticatorMethods {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/microsoftAuthenticatorMethods") -authToken $authToken -onlyValues $true
    
}

# These currently only work in "beta"
function Get-AADUserPhoneAuthMethods {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/beta/"
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/phoneMethods") -authToken $authToken -onlyValues $true
   
}


function Remove-AADUserMSAuthenticatorMethod {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        [Parameter(Mandatory = $true)]
        [string] $authId,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "users"
    
    Invoke-GraphRestRequest -method "DELETE" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/microsoftAuthenticatorMethods/" + $authId) -authToken $authToken -onlyValues $true
}

# Currently only works in "beta"
function Remove-AADUserPhoneAuthMethod {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        [Parameter(Mandatory = $true)]
        [string] $authId,
        $prefix = "https://graph.microsoft.com/beta/"
    )

    $resource = "users"
    
    Invoke-GraphRestRequest -method "DELETE" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/phoneMethods/" + $authId) -authToken $authToken -onlyValues $true
}

# Currently only works with beta endpoint
# Will fail if a phoneAuthMethod already exists.
# TODO: support "alternateMobile" phoneType?
function Add-AADUserPhoneAuthMethod {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        [Parameter(Mandatory = $true)]
        [string] $phoneNumber,
        $prefix = "https://graph.microsoft.com/beta/"
    )

    $resource = "users"

    # Check if a phoneAuthMethod already exists
    $method = Get-AADUserPhoneAuthMethods -userID $userID -authToken $authToken
    if ($method) {
        throw "User already has a phoneAuthMethod."
    }

    $body = @"
{
  "phoneNumber": "$phoneNumber",
  "phoneType": "mobile"
}
"@

    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/phoneMethods") -body $body -authToken $authToken -onlyValues $true
}

# Currently only works in "beta"
# Will update a phoneAuthenticationMethod to a new phone number
function Update-AADUserPhoneAuthMethod {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        [string] $authId = $null,
        [Parameter(Mandatory = $true)]
        [string] $phoneNumber,
        $prefix = "https://graph.microsoft.com/beta/",
        [bool]$createIfNeeded = $false
    )

    # If no authId is given, update primary one.
    if (-not $authId) {
        $method = Get-AADUserPhoneAuthMethods -userID $userID -authToken $authToken
        if ($method) {
            $authId = ($method | Where-Object { $_.phoneType -eq "mobile" }).id
        }
        else {
            Write-Output "No phoneAuthMethod exists."
            if ($createIfNeeded) {
                Write-Output "Adding as new phoneAuthMethod."
                Add-AADUserPhoneAuthMethod -authToken $authToken -userID $userID -phoneNumber $phoneNumber
                return
            }
            else {
                throw "Can not update a non existing phoneAuthMethod. "
            }
            
        }
    }

    $resource = "users"

    $body = @"
{
  "phoneNumber": "$phoneNumber",
  "phoneType": "mobile"
}
"@

    
    Invoke-GraphRestRequest -method "PUT" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/phoneMethods/" + $authId) -body $body -authToken $authToken -onlyValues $true
}

# This will reset a user's password. The new password will be returned in clear text(!). The user should immediately change that password.
#
# Currently only available in MS Graph BETA Api
function Get-AADUserPasswordAuthMethods {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/beta/"
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/passwordMethods") -authToken $authToken -onlyValues $true
}

function Update-AADUserProperty {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        [Parameter(Mandatory = $true)]
        [string] $property,
        # Can be a string or array
        [Parameter(Mandatory = $true)]
        $value,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "users"

    $body = (@{$property = $value } | ConvertTo-Json -Depth 6)

    Invoke-GraphRestRequest -method "PATCH" -prefix $prefix -body $body -resource ($resource + "/" + $userID) -authToken $authToken -onlyValues $false

}

# This will reset a user's password. The new password will be returned in clear text(!). The user should immediately change that password.
#
# Currently only available in MS Graph BETA Api
# Currently not supported using AppPermissions, only in delegated operation.
function Reset-AADUserPasswordAuthMethod {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/beta/"
    )
    
    # Let us assume there is only one password per user, as described in https://docs.microsoft.com/en-us/graph/api/authentication-list-passwordmethods?view=graph-rest-beta&tabs=http
    $method = Get-AADUserPasswordAuthMethods -authToken $authToken -userID $userID -prefix $prefix
    if (-not $method) {
        throw "No password auth method found. Is this a regular user?"
    }

    # Can not operate on blocked users. Check!
    if (-not (get-AADUserIsEnabled -authToken $authToken -userId $userID)) {
        # Make sure, user is enabled
        update-AADUserProperty -userID $userID -property "accountEnabled" -value "True" -authToken $authToken
    }

    $resource = "users"
    
    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/passwordMethods/" + $method.id + "/resetPassword") -authToken $authToken -onlyValues $true
}

# Currently only possible with beta API
function Get-AADUserTemporaryAccessPass {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/beta/"
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/temporaryAccessPassMethods") -authToken $authToken -onlyValues $true
}

# Currently only possible with beta API
function Remove-AADUserTemporaryAccessPass {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        $prefix = "https://graph.microsoft.com/beta/"
    )

    # Current assumption: A user can only have one temp. access pass. But well, be safe.
    $authId = Get-AADUserTemporaryAccessPass -authToken $authToken -userID $userID -prefix $prefix

    $resource = "users"

    $authId | ForEach-Object {
        Invoke-GraphRestRequest -method "DELETE" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/temporaryAccessPassMethods/" + $_.id) -authToken $authToken -onlyValues $true
    }

}

# Currently only possible with beta API
# Currently does not implement "delayed start time"
function New-AADUserTemporaryAccessPass {
    param (
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string] $userID,
        [bool] $oneTimeUse = $false,
        [int]$lifetimeInMinutes = "120",
        $prefix = "https://graph.microsoft.com/beta/"
    )

    # Make sure no other temp. access pass exists
    Remove-AADUserTemporaryAccessPass -authToken $authToken -userID $userID -prefix $prefix

    $resource = "users"

    $body = @{
        "@odata.type"       = "#microsoft.graph.temporaryAccessPassAuthenticationMethod";
        "lifetimeInMinutes" = $lifetimeInMinutes;
        "isUsableOnce"      = $oneTimeUse
    }

    $json = $body | ConvertTo-Json -Depth 6

    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource ($resource + "/" + $userID + "/authentication/temporaryAccessPassMethods") -body $json -authToken $authToken -onlyValues $false

}

#endregion

#region identityProtection

function Get-RiskyUsers {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0"
    )

    $resource = "/identityProtection/riskyUsers"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource $resource -authToken $authToken -onlyValues $true
}

function Set-DismissRiskyUser {
    param(
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string]$userId, 
        $prefix = "https://graph.microsoft.com/v1.0"
    )

    $resource = "/identityProtection/riskyUsers/dismiss"

    $body = (@{ "userIds" = ([array]$userId) } | ConvertTo-Json -Depth 6 )
    
    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource $resource -body $body -authToken $authToken -onlyValues $false
}

function Set-ConfirmCompromisedRiskyUser {
    param(
        $authToken = $null,
        [Parameter(Mandatory = $true)]
        [string]$userId, 
        $prefix = "https://graph.microsoft.com/v1.0"
    )

    $resource = "/identityProtection/riskyUsers/confirmCompromised"

    $body = (@{ "userIds" = ([array]$userId) } | ConvertTo-Json -Depth 6 )
    
    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource $resource -body $body -authToken $authToken -onlyValues $false
}
#endregion

#region Users


function Get-AADUsers {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource $resource -authToken $authToken -onlyValues $true
}

function Get-AADUserIsEnabled {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        $userId,
        [switch]$WhatIf = $false
    )

    $resource = "users"

    $query = ($resource + "/" + $userID + "?`$select=displayName,accountEnabled")
    if ($WhatIf) { "query: " + $prefix + $query }

    if (-not $WhatIf) {
        $result = Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource $query -authToken $authToken -onlyValues $false
        if ($result.accountEnabled -eq "True") {
            return $true
        }
        else {
            return $false
        }
    }
}

function Get-AADUserByUPN {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        $userName
    )

    get-AADUserByID -authToken $authToken -prefix $prefix -userID $userName
}

function Get-AADUserByID {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        [string]$userID
    )

    $resource = "users"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource ($resource + "/" + $userID ) -authToken $authToken -onlyValues $false
}

function Remove-AADUserById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        [Parameter(Mandatory = $true)]
        [string]$userID,
        [switch]$force = $false
    )

    $resource = "users"

    # Be really sure about this one.
    if ($force) {
        Invoke-GraphRestRequest -method "DELETE" -prefix $prefix -resource ($resource + "/" + $userID ) -authToken $authToken -onlyValues $false
    }
    else {
        "No action taken. Use -force to enforce a user deletion."
    }
}

#endregion

#region Compliance Policies

function Get-CompliancePolicies {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $resource = "deviceManagement/deviceCompliancePolicies"
        
    )

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"   
   
}

function Get-CompliancePolicyByName {
    param (
        $authToken = $null,
        $policyName = $null
    )

    $policies = Get-CompliancePolicies -authToken $token

    $policies | Where-Object { $_.displayName -like $policyName }

}

function Add-CompliancePolicy {
    param (
        $policy = $null,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $action = "block",
        $gracePeriodHours = 12
    )

    $resource = "deviceManagement/deviceCompliancePolicies"

    if ($null -eq $policy) {
        "Please provide a Compliance Policy. You can create those using Import-PolicyObject."
        return
    }

    $policy = $policy | Select-Object -Property * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version

    if ($null -eq $policy.scheduledActionsForRule) {
        # Adding Scheduled Actions Rule to JSON
        Add-Member -InputObject $policy -MemberType NoteProperty -Name "scheduledActionsForRule" -Value $null
        $policy.scheduledActionsForRule = [array]@{
            ruleName                      = "NonCompliantRule1"
            scheduledActionConfigurations = [array]@{
                actionType                = $action
                gracePeriodHours          = $gracePeriodHours
                notificationTemplateId    = ""
                notificationMessageCCList = [array]@()
            }
        }
    }

    $JSON = $policy | ConvertTo-Json -Depth 6

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "POST" -body $JSON -onlyValues $false
}

#TODO: Currently overwrites all other assignments
function Set-CompliancePolicyToGroupAssignment {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $CompliancePolicyId = "",
        $TargetGroupId = "",
        $TargetGroupName = ""
    )

    if ($CompliancePolicyId -eq "") {
        "Please provide a Compliance Policy ID. You get those from the objects returned by Get-CompliancePolicies"
        return
    }

    if (($TargetGroupId -eq "") -and ($TargetGroupName -eq "")) {
        "Please provide an AzureAD group ID or DisplayName. You can get those from the objects returned by Get-AADGroups"
        return
    }

    if ($TargetGroupId -eq "") {
        $TargetGroupId = (Get-AADGroupByName -groupName $TargetGroupName -authToken $authToken).id
    }
 
    $resource = "deviceManagement/deviceCompliancePolicies/$CompliancePolicyId/assign"

    $JSON = @"

    {
        "assignments": [
        {
            "target": {
            "@odata.type": "#microsoft.graph.groupAssignmentTarget",
            "groupId": "$TargetGroupId"
            }
        }
        ]
    }
    
"@


    ##TODO: This should work. Why does it not?
    #$resource = "deviceManagement/deviceCompliancePolicies/" + $CompliancePolicyId + "/assignments"

    #$JSON = '{
    # "target": {
    # "@odata.type": "#microsoft.graph.groupAssignmentTarget",
    # "groupId": "' + $TargetGroupId + '"
    # }
    #}'


    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource $resource -body $JSON -authToken $authToken -onlyValues $false

}

#TODO: Currently overwrites all other assignments. Not usefull. Leaving it private for now.
#FIXME
function Set-CompliancePolicyFromGroupExclusion {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $CompliancePolicyId = "",
        $TargetGroupId = "",
        $TargetGroupName = ""
    )

    if ($CompliancePolicyId -eq "") {
        "Please provide a Compliance Policy ID. You get those from the objects returned by Get-CompliancePolicies"
        return
    }

    if (($TargetGroupId -eq "") -and ($TargetGroupName -eq "")) {
        "Please provide an AzureAD group ID or DisplayName. You can get those from the objects returned by Get-AADGroups"
        return
    }

    if ($TargetGroupId -eq "") {
        $TargetGroupId = (Get-AADGroupByName -groupName $TargetGroupName -authToken $authToken).id
    }
 
    $resource = "deviceManagement/deviceCompliancePolicies/$CompliancePolicyId/assign"

    $JSON = @"

    {
        "assignments": [
        {
            "target": {
            "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget",
            "groupId": "$TargetGroupId"
            }
        }
        ]
    }
    
"@


    Invoke-GraphRestRequest -method "POST" -prefix $prefix -resource $resource -body $JSON -authToken $authToken

}

#endregion

#region Conditional Access Policies
function Get-ConditionalAccessPolicies {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "identity/conditionalAccess/policies"

    Invoke-GraphRestRequest -method "GET" -prefix $prefix -resource $resource -authToken $authToken
}

function Add-ConditionalAccessPolicy {
    param(
        $authToken = $null,
        $policy = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "identity/conditionalAccess/policies"

    if ($null -eq $policy) {
        "Please provide a Conditional Access Policy. You can create those using Import-PolicyObject."
        return
    }

    $policy = $policy | Select-Object -Property * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version

    $JSON = $policy | ConvertTo-Json -Depth 6

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "POST" -body $JSON -onlyValues $false
}

#TODO: Allow to assign to groups by name

#endregion

#region Device Configurations

# Get/Fetch all existing Device Configuration Policies (the old ones, not the newer Config Settings)
# Has only been tested with "Beta" endpoint. Retest with "v1.0"
function Get-DeviceConfigurations {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = "deviceManagement/deviceConfigurations"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"

}

# Get/Fetch an existing Device Configuration Policy by its ID (not Config Settings)
# Has only been tested with "Beta" endpoint. Retest with "v1.0"
function Get-DeviceConfigurationById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $configId = ""
    )

    $resource = "deviceManagement/deviceConfigurations"

    if ($configId -eq "") {
        "Please provide an Device Configuration UID. You can get those from the results from Get-DeviceConfigurations"
    }

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource ($resource + "/" + $configId) -method "GET" -onlyValues $false

}

function Get-DeviceConfigurationAssignmentById {
    param(
        $authToken = $null,
        #V1.0 does not support all current data fields...
        $prefix = "https://graph.microsoft.com/Beta/",
        $configId = ""
    )

    $resource = "deviceManagement/deviceConfigurations"

    if ($configId -eq "") {
        "Please provide an Device Configuration UID. You can get those from the results from Get-DeviceConfigurations"
        return
    }

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource ($resource + "/" + $configId + "/assignments") -method "GET"
}

function Export-DeviceConfigurationsAndAssignments {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $configs = Get-DeviceConfigurations -authToken $authToken -prefix $prefix

    Export-PolicyObjects -policies $configs

    $configs | ForEach-Object {
        $assignment = Get-DeviceConfigurationAssignmentById -authToken $authToken -prefix $prefix -configId $_.Id
        $name = $_.displayName -replace "[$([RegEx]::Escape([string][IO.Path]::GetInvalidFileNameChars()))]+", "_"
        $filename = $name + "_assignment.json"
        if (test-path -Path $filename) {
            $filename + " exists. Will not overwrite."
        }
        else {
            $assignment | ConvertTo-Json -Depth 6 > $filename
        }
        
    }

}

# Import/Create a new Device Configuration Policy
# TODO: Retest with v1.0
# ... if you can use the newer "Config Settings"...
function Add-DeviceConfiguration {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/beta/",
        $config = $null
    )

    $resource = "deviceManagement/deviceConfigurations"

    if ($null -eq $config) {
        "Please provide a Device Configuration. You can create those using Import-PolicyObject."
        return
    }

    $config = $config | Select-Object -Property * -ExcludeProperty id, createdDateTime, lastModifiedDateTime, version

    $JSON = $config | ConvertTo-Json -Depth 6

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "POST" -body $JSON -onlyValues $false

}

function Set-DeviceConfigurationAssignment {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $configId = "",
        $target = $null
    )
    
    $resource = "deviceManagement/deviceConfigurations"

    if ($null -eq $target) {
        return "Please provide a target for the device configuration, like #microsoft.graph.allDevicesAssignmentTarget or pass an AssignmentTarget Object"
    }

    if ($configId -eq "") {
        return "Please provide a Device Configuration Id." 
    }

    if ($target.getType().Name -eq "String") {
        $JSON = '{
    "target": {
        "@odata.type": "'
 + $target + '"
    }
}'

    }
    else {
        $JSON = $target | ConvertTo-Json -Depth 6 
    }

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource ($resource + "/" + $configId + "/assignments") -method "POST" -body $JSON -onlyValues $false
}

function Set-DeviceConfigurationAssignmentToGroup {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $configId = "",
        $TargetGroupId = "",
        $TargetGroupName = ""
    )

    if ($configId -eq "") {
        "Please provide a Device Configuration ID. You get those from the objects returned by Get-DeviceConfigurations."
        return
    }

    if (($TargetGroupId -eq "") -and ($TargetGroupName -eq "")) {
        "Please provide an AzureAD group ID or DisplayName. You can get those from the objects returned by Get-AADGroups"
        return
    }

    if ($TargetGroupId -eq "") {
        $TargetGroupId = (Get-AADGroupByName -groupName $TargetGroupName -authToken $authToken).id
    }
 
    $target = @"
{
    "target": {
    "@odata.type": "#microsoft.graph.groupAssignmentTarget",
    "groupId": "$TargetGroupId"
    }
}
"@
 | ConvertFrom-Json -Depth 6

    Set-DeviceConfigurationAssignment -configId $configId -authToken $authToken -prefix $prefix -target $target

}

function Set-DeviceConfigurationFromGroupExclusion {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $configId = "",
        $TargetGroupId = "",
        $TargetGroupName = ""
    )

    if ($configId -eq "") {
        "Please provide a Device Configuration ID. You get those from the objects returned by Get-DeviceConfigurations."
        return
    }

    if (($TargetGroupId -eq "") -and ($TargetGroupName -eq "")) {
        "Please provide an AzureAD group ID or DisplayName. You can get those from the objects returned by Get-AADGroups"
        return
    }

    if ($TargetGroupId -eq "") {
        $TargetGroupId = (Get-AADGroupByName -groupName $TargetGroupName -authToken $authToken).id
    }
 
    $target = @"
{
    "target": {
    "@odata.type": "#microsoft.graph.exclusionGroupAssignmentTarget",
    "groupId": "$TargetGroupId"
    }
}
"@
 | ConvertFrom-Json -Depth 6

    Set-DeviceConfigurationAssignment -configId $configId -authToken $authToken -prefix $prefix -target $target

}

#endregion

#region Device configurationPolicies / Device Settings (the newer ones, in contrast to the older ConfigProfiles / DeviceConfigurations)
function Get-DeviceConfigurationPolicies {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = "deviceManagement/configurationPolicies"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"

}

function Get-DeviceConfigurationPolicySettingsById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/",
        $id = ""
    )

    $resource = "deviceManagement/configurationPolicies/" + $id + "/settings"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

#TODO: Implement Add--DeviceConfigurationPolicy

#endregion

function Get-AADRoleTemplateById {
    param(
        $authToken,
        [Parameter(Mandatory = $true)]
        $id
    )

    Invoke-GraphRestRequest -resource "/directoryRoleTemplates/$id" -onlyValues $false -authToken $authToken
}

function Get-ConditionalAccessNamedLocationById {
    param(
        $authToken,
        [Parameter(Mandatory = $true)]
        $id
    )
    Invoke-GraphRestRequest -resource "/identity/conditionalAccess/namedLocations/$id" -onlyValues $false -authToken $authToken
}

function Test-GUID {
    param(
        [Parameter(Mandatory = $true)]
        [string] $guidCandidate
    )

    [guid]::TryParse($guidCandidate, $([ref][guid]::Empty))
}

#region App registration and Service Principal

# Get App registrations
function Get-AADApps {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "/applications"

    Invoke-GraphRestRequest -method "GET" -authToken $authToken -prefix $prefix -resource $resource

}

function Get-AADAppById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $id = ""
    )

    if ($id -eq "") {
        return "Please provied an Azure AD App ID"
    }
    
    $resource = "/applications/" + $id

    Invoke-GraphRestRequest -method "GET" -authToken $authToken -prefix $prefix -resource $resource -onlyValues $false
}

function Add-AADApp {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $appObject = $null    
    )

    if ($null -eq $appObject) {
        return "Please provide an Azure AD App description object"
    }

    $resource = "/applications"

    $JSON = $appObject | ConvertTo-Json -Depth 6 

    Invoke-GraphRestRequest -method "POST" -authToken $authToken -prefix $prefix -resource $resource -body $JSON -onlyValues $false
        
}

function Remove-AADAppById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $id = ""
    )

    if ($id -eq "") {
        return "Please provied an Azure AD App ID"
    }
    
    $resource = "/applications/" + $id

    Invoke-GraphRestRequest -method "DELETE" -authToken $authToken -prefix $prefix -resource $resource -onlyValues $false
}

function Add-AADAppPassword {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $secretFriendlyName = "automation credential",
        $appId = ""
    )

    if ($appId -eq "") {
        return "Plese provide an application id"
    }

    $resource = "/applications/" + $appId + "/addPassword"

    $JSON = @"
    {
        "passwordCredential": {
            "displayName": "$secretFriendlyName"
        }
    }
"@


    Invoke-GraphRestRequest -method "POST" -authToken $authToken -resource $resource -body $JSON -onlyValues $false
}


# Get Service Principals.. also known as "Enterprise Apps" in portal
function Get-ServicePrincipals {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "/servicePrincipals"

    Invoke-GraphRestRequest -method "GET" -authToken $authToken -prefix $prefix -resource $resource
}


function Add-ServicePrincipalByAppId {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $id = ""
    )

    if ($id -eq "") {
        return "Please provied an Azure AD App ID"
    }
    
    $resource = "/servicePrincipals"

    $JSON = @"
    {
        "appId": "$id"
    }
"@

    
    Invoke-GraphRestRequest -method "POST" -authToken $authToken -prefix $prefix -resource $resource -body $JSON -onlyValues $false
}

# List App Permissions
function Get-ServicePrincipalAppRoleAssignmentsById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $id = ""
    )

    if ($id -eq "") {
        return "Please provied an Azure AD App ID"
    }
    
    $resource = "/servicePrincipals/" + $id + "/appRoleAssignments"

    Invoke-GraphRestRequest -method "GET" -authToken $authToken -prefix $prefix -resource $resource
}

function Add-ServicePrincipalAppRoleAssignment {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $permissionObject = $null
    )

    if ($null -eq $permissionObject) {
        return "Please provide an Enterprise App (Service Principal) permission / role assignment"
    }

    $resource = "/servicePrincipals/" + $permissionObject.principalId + "/appRoleAssignments"

    $JSON = $permissionObject | ConvertTo-Json -Depth 6

    Invoke-GraphRestRequest -method "POST" -authToken $authToken -prefix $prefix -resource $resource -body $JSON -onlyValues $false
}

function Get-AADRoleById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $id = ""
    )

    $resource = "/directoryRoles/$id"

    Invoke-GraphRestRequest -method GET -authToken $authToken -prefix $prefix -resource $resource -onlyValues $true

}

function Add-ServicePrincipalPassword {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/",
        $secretFriendlyName = "automation credential",
        $servicePrincipalId = ""
    )

    if ($servicePrincipalId -eq "") {
        return "Plese provide a service principal id"
    }

    $resource = "/servicePrincipals/" + $servicePrincipalId + "/addPassword"

    $JSON = @"
    {
        "passwordCredential": {
            "displayName": "$secretFriendlyName"
        }
    }
"@


    Invoke-GraphRestRequest -method "POST" -authToken $authToken -resource $resource -body $JSON -onlyValues $false
}

#endregion

#region Windows Autopilot profiles

function Get-AutopilotProfiles {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = "deviceManagement/windowsAutopilotDeploymentProfiles"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

#TODO: Add-AutopilotProfiles

#endregion

#region (Mobile) Imported Devices

function Get-ImportedMobileDevices {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = "/deviceManagement/importedDeviceIdentities"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

#endregion

#region Autopilot Devices

function Get-ImportedAutopilotDevices {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = "/deviceManagement/importedWindowsAutopilotDeviceIdentities"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

function Get-WindowsAutopilotDevices {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "/deviceManagement/windowsAutopilotDeviceIdentities"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

function Get-WindowsAutopilotDeviceByDeviceId {
    param(
        [Parameter(Mandatory = $true)]
        [string]$azureAdDeviceId,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    ## Filtering seems not to work yet on this resource.
    #$resource = "/deviceManagement/windowsAutopilotDeviceIdentities`?`$filter=azureAdDeviceId eq `'$azureAdDeviceId`'"
    #Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"

    ## Let's do it the hard way then...
    Get-WindowsAutopilotDevices -authToken $authToken -prefix $prefix | Where-Object { $_.azureAdDeviceId -eq $azureAdDeviceId }
}

function Remove-WindowsAutopilotDevice {
    param(
        [Parameter(Mandatory = $true)]
        [string]$id,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = "/deviceManagement/windowsAutopilotDeviceIdentities/$id"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "DELETE" -onlyValues $false
}


#endregion

#region Managed Devices
function Get-ManagedDevices {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "/deviceManagement/managedDevices"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

function Get-ManagedDeviceByDeviceId {
    param(
        [Parameter(Mandatory = $true)]
        [string]$azureAdDeviceId,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "/deviceManagement/managedDevices`?`$filter=azureADDeviceId eq `'$azureAdDeviceId`'"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

function Remove-ManagedDevice {
    param(
        [Parameter(Mandatory = $true)]
        [string]$id,
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/v1.0/"
    )

    $resource = "/deviceManagement/managedDevices/$id"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "DELETE" -onlyValues $false
}

#endregion

#region Sites and Files (OneDrive, OneDriveFB/SharePoint)

function Get-SPRootSite {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"

    )

    $resource = "/sites/root"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $false
}

function Get-SPAllSites {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/Beta/"
    )

    $resource = '/sites'

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $true
}

function Get-SPSite {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $hostname = "",
        $site = ""
    )

    if (($site -eq "") -or ($hostname -eq "")) {
        return "Please give Hostname and SharePoint Site Name."
    }

    $resource = '/sites/' + $hostname + ":/sites/" + $site

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $false
}


function Get-SPSiteDriveById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $siteId = ""
    )

    if ($siteId -eq "") {
        return "Please give your SharePoint Sites ID"
    }

    $resource = '/sites/' + $siteId + "/drive"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $false
}

function Get-DriveById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $driveId = ""
    )

    if ($driveId -eq "") {
        return "Please give your (OneDrive) Drive ID"
    }

    $resource = "/drives/" + $driveId

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $false
}

function Get-DriveChildrenByPath {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $driveId = "",
        $path = "root"
    )

    if ($driveId -eq "") {
        return "Please give your (OneDrive) Drive ID"
    }

    $resource = "/drives/" + $driveId + "/" + $path + "/children"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $true
}

function Get-DriveItemVersions {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $driveId = "",
        $itemId = ""
    )

    if (($driveId -eq "") -or ($itemId -eq "")) {
        return "Please give your (OneDrive) Drive ID and Item ID"
    }

    $resource = "/drives/" + $driveId + "/items/" + $itemId + "/versions"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET" -onlyValues $true
}

function Get-MyDrives {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "/me/drives"

    Invoke-GraphRestRequest -authToken $authToken -prefix $prefix -resource $resource -method "GET"
}

#endregion

#region Calendars (using Delegated Permissions)

function Get-MyCalendars {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"

    )

    $resource = "/me/calendars"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true

}

function Get-CalendarEvents {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $calendarId = ""
    )

    if ($calendarId -eq "") {
        return "Please give a Calendar ID"
    }

    $resource = "/me/calendars/" + $calendarId + "/events"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true
}

#endregion

#region Mail (using Delegated Permissions)
function Get-MyMails {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = '/me/messages'

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true

}

function Get-MyMailById {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $Id = ""
    )

    if ($Id -eq "") {
        return "Please give a mail/message ID"
    }

    $resource = '/me/messages/' + $Id + '/$value'

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $false

}

#endregion

#region Teams

function Get-MyTeams {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/"
    )

    $resource = "/me/joinedTeams"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true
}

function Get-TeamsChannels {
    param(
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $teamId = "" 
    )

    if ($teamId -eq "") {
        return "Please provide a MS Teams Team ID"
    }

    $resource = "/teams/" + $teamId + "/channels"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true

}

function  Get-TeamsChannelMessages {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $teamId = "",
        $channelId = "" 
    )
    
    if (($teamId -eq "") -or ($channelId -eq "")) {
        return "Please provide a MS Teams Team and Channel ID"
    }

    $resource = "/teams/" + $teamId + "/channels/" + $channelId + "/messages"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true
}


function  Get-TeamsChannelMessageById {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $teamId = "",
        $channelId = "",
        $messageId = "" 
    )
    
    if (($teamId -eq "") -or ($channelId -eq "") -or ($messageId -eq "")) {
        return "Please provide a MS Teams Team, Channel and message ID"
    }

    $resource = "/teams/" + $teamId + "/channels/" + $channelId + "/messages/" + $messageId

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $false
}

function  Get-TeamsChannelMessageHostedContents {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $teamId = "",
        $channelId = "",
        $messageId = "" 
    )

    if (($teamId -eq "") -or ($channelId -eq "") -or ($messageId -eq "")) {
        return "Please provide a MS Teams Team, Channel and message ID"
    }
    
    $resource = "/teams/" + $teamId + "/channels/" + $channelId + "/messages/" + $messageId + "/hostedContents"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true
}

function  Get-TeamsChannelMessageHostedContentsById {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/beta/",
        $teamId = "",
        $channelId = "",
        $messageId = "",
        $hostedContentsId = "" 
    )

    if (($teamId -eq "") -or ($channelId -eq "") -or ($messageId -eq "") -or ($hostedContentsId -eq "")) {
        return "Please provide a MS Teams Team, Channel, Message and HostedContent ID"
    }
    
    $resource = "/teams/" + $teamId + "/channels/" + $channelId + "/messages/" + $messageId + "/hostedContents/" + $hostedContentsId + '/$value'

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $false -writeToFile $true -outFile "image.png"
}


function Get-TeamsChannelMessageReplies {
    param (
        $authToken = $null,
        $prefix = "https://graph.microsoft.com/V1.0/",
        $teamId = "",
        $channelId = "",
        $messageId = "" 
    )

    if (($teamId -eq "") -or ($channelId -eq "") -or ($messageId -eq "")) {
        return "Please provide a MS Teams Team, Channel and message ID"
    }
    
    $resource = "/teams/" + $teamId + "/channels/" + $channelId + "/messages/" + $messageId + "/replies"

    Invoke-GraphRestRequest -prefix $prefix -resource $resource -method "GET" -authToken $authToken -onlyValues $true

}

#endregion

#region Mermaid export
function Resolve-AppName {
    param(
        $authToken,
        [string]$appId
    )

    # Well known Office apps:
    $appMapping = @{
        "00000002-0000-0000-c000-000000000000" = "AAD Graph API"    
        "00000002-0000-0ff1-ce00-000000000000" = "Office 365 Exchange Online"
        "00000003-0000-0000-c000-000000000000" = "Microsoft Graph"
        "00000004-0000-0ff1-ce00-000000000000" = "Skype for Business Online"
        "00000005-0000-0ff1-ce00-000000000000" = "Office 365 Yammer"
        "2d4d3d8e-2be3-4bef-9f87-7875a61c29de" = "OneNote"
        "797f4846-ba00-4fd7-ba43-dac1f8f63013" = "Windows Azure Service Management API"
        "c5393580-f805-4401-95e8-94b7a6ef2fc2" = "Office 365 Management APIs"
        "cc15fd57-2c6c-4117-a88c-83b1d56b4bbe" = "Microsoft Teams Services"
        "cfa8b339-82a2-471a-a3c9-0fc0be7a4093" = "Azure Key Vault"
        "All"                                  = "All"
    }

    if ($appMapping.ContainsKey($appId)) {
        $appMapping[$appId]
    }
    else {
        $app = Invoke-GraphRestRequest -authToken $authToken -resource "/applications?`$filter=appId eq '$appId'"
        if ($app) {
            $app.displayName
        }
        else {
            $appId
        }
    }
}

function Resolve-Username {
    param(
        $authToken,
        [Parameter(Mandatory = $true)]
        [string] $userId
    )

    if (Test-GUID -guidCandidate $userId) {
        $user = Get-AADUserByID -userID $_ -ErrorAction SilentlyContinue -authToken $authToken
        if ($user -and $user.userPrincipalName) {
            $user.userPrincipalName
        }
        else {
            $userId
        }
    }
    else {
        $userId
    }
}

function Resolve-Groupname {
    param(
        $authToken,
        [Parameter(Mandatory = $true)]
        [string] $groupId
    )

    if (Test-GUID -guidCandidate $groupId) {
        $group = Get-AADGroupByID -groupID $_ -ErrorAction SilentlyContinue -authToken $authToken
        if ($group -and $group.displayName) {
            $group.displayName
        }
        else {
            $groupId
        }
    }
    else {
        $groupId
    }
}

function Resolve-RoleTemplateName {
    param(
        $authToken,
        [Parameter(Mandatory = $true)]
        [string] $roleTemplateId
    )

    if (Test-GUID -guidCandidate $roleTemplateId) {
        $roleTemplate = Get-AADRoleTemplateById -id $roleTemplateId -ErrorAction SilentlyContinue -authToken $authToken
        if ($roleTemplate) {
            $roleTemplate.displayName
        }
        else {
            $roleTemplateId
        }
    }
    else {
        $roleTemplateId
    }
}

function Resolve-ConditionalAccessNamedLocationById {
    param(
        $authToken,
        [Parameter(Mandatory = $true)]
        $id
    )

    if (Test-GUID -guidCandidate $id) {
        $namedLocation = Get-ConditionalAccessNamedLocationById -id $id -ErrorAction SilentlyContinue -authToken $authToken
        if ($namedLocation -and $namedLocation.displayName) {
            $namedLocation.displayName
        }
        else {
            $id
        }
    }
    else {
        $id
    }
}

function Write-ConditionalAccessPolicyToMermaid {
    param(
        # Conditional Access Policy
        $pol,
        # Write HTML files (with mermaid rendered in the browser)
        [bool] $asHTML = $true,
        # Write Markdown. If both are present, HTML is prefered.
        [bool] $asMarkdown = $false,
        $authToken
    )

    if ($asHTML) {
        @'
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
</head>
<body>
<div class="mermaid">
'@

    }
    elseif ($asMarkdown) {
        @'
::: mermaid
'@

    }

    "graph LR;"
    "id[`"$($pol.displayName)`"] -----> state[state: $($pol.state)]"

    if ($pol.conditions) {
        'id --> conditions'

        if ($pol.conditions.applications) {
            'conditions --> applications'
        
            if ($pol.conditions.applications.includeApplications) {
                'applications --> includeApplications'

                $pol.conditions.applications.includeApplications | ForEach-Object {
                    
                    "includeApplications --> a_$($_)[`"$(Resolve-AppName -appId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.applications.excludeApplications) {
                'applications --> excludeApplications'

                $pol.conditions.applications.excludeApplications | ForEach-Object {
                    
                    "excludeApplications --> a_$($_)[`"$(Resolve-AppName -appId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.applications.includeUserActions) {
                'applications --> includeUserActions'

                $pol.conditions.applications.includeUserActions 
            }

        }

        if ($pol.conditions.users) {
            'conditions --> users'

            if ($pol.conditions.users.includeUsers) {
                'users --> includeUsers'

                $pol.conditions.users.includeUsers | ForEach-Object {
                    "includeUsers --> u_$($_)[`"$(Resolve-Username -userId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.users.excludeUsers) {
                'users --> excludeUsers'

                $pol.conditions.users.excludeUsers | ForEach-Object {
                    "excludeUsers --> u_$($_)[`"$(Resolve-Username -userId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.users.includeGroups) {
                'users --> includeGroups'

                $pol.conditions.users.includeGroups | ForEach-Object {
                    "includeGroups --> g_$($_)[`"$(Resolve-Groupname -groupId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.users.excludeGroups) {
                'users --> excludeGroups'

                $pol.conditions.users.excludeGroups | ForEach-Object {
                    "excludeGroups --> g_$($_)[`"$(Resolve-Groupname -groupId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.users.includeRoles) {
                'users --> includeRoles'

                $pol.conditions.users.includeRoles | ForEach-Object {
                    "includeRoles --> r_$($_)[`"$(Resolve-RoleTemplateName -roleTemplateId $_ -authToken $authToken)`"]"
                }
            }

            if ($pol.conditions.users.excludeRoles) {
                'users --> excludeRoles'

                $pol.conditions.users.excludeRoles | ForEach-Object {
                    "excludeRoles --> r_$($_)[`"$(Resolve-RoleTemplateName -roleTemplateId $_ -authToken $authToken)`"]"
                }
            }
        }

        if ($pol.conditions.clientAppTypes) {
            'conditions ---> clientAppTypes'

            $pol.conditions.clientAppTypes | ForEach-Object {
                "clientAppTypes --> $($_)"
            }
        }

        if ($pol.conditions.locations) {
            'conditions --> locations'

            if ($pol.conditions.locations.includeLocations) {
                'locations --> includeLocations'
            
                $pol.conditions.locations.includeLocations | ForEach-Object {
                    "includeLocations --> l_$($_)[`"$(Resolve-ConditionalAccessNamedLocationById -id $_ -authToken $authToken)`"]"
                }
            }        

            if ($pol.conditions.locations.excludeLocations) {
                'locations --> excludeLocations'
            
                $pol.conditions.locations.excludeLocations | ForEach-Object {
                    "excludeLocations --> l_$($_)[`"$(Resolve-ConditionalAccessNamedLocationById -id $_ -authToken $authToken)`"]"
                }
            }        
        }

        if ($pol.conditions.platforms) {
            'conditions --> platforms'

            if ($pol.conditions.platforms.includePlatforms) {
                'platforms --> includePlatforms'

                $pol.conditions.platforms.includePlatforms | ForEach-Object {
                    "includePlatforms --> cap_$($_)[$($_)]"
                }
            }

            if ($pol.conditions.platforms.excludePlatforms) {
                'platforms --> excludePlatforms'

                $pol.conditions.platforms.excludePlatforms | ForEach-Object {
                    "excludePlatforms --> cap_$($_)[$($_)]"
                }
            }
        }

        if ($pol.conditions.signInRiskLevels) {
            'conditions --> signInRiskLevels'

            $pol.conditions.signInRiskLevels | ForEach-Object {
                "signInRiskLevels --> sirl_$($_)[$($_)]"
            }
        }

        if ($pol.conditions.userRiskLevels) {
            'conditions --> userRiskLevels'

            $pol.conditions.userRiskLevels | ForEach-Object {
                "userRiskLevels --> url_$($_)[$($_)]"
            }
        }
    }

    if ($pol.grantControls) {
        'id ---> grantControls'
        "grantControls --> grantControlsOperator[operator: $($pol.grantControls.operator)]"

        if ($pol.grantControls.builtInControls) {
            'grantControls --> builtInControls'

            $pol.grantControls.builtInControls | ForEach-Object {
                "builtInControls --> bic_$($_)[$($_)]"
            }
        }

        if ($pol.grantControls.customAuthenticationFactors) {
            'grantControls --> customAuthenticationFactors'

            $pol.grantControls.customAuthenticationFactors | ForEach-Object {
                "customAuthenticationFactors --> cam_$($_)[$($_)]"
            }
        }

        if ($pol.grantControls.termsOfUse) {
            'grantControls --> termsOfUse'

            $pol.grantControls.termsOfUse | ForEach-Object {
                "termsOfUse --> tou_$($_)[$($_)]"
            }

        } 
    }
    if ($pol.sessionControls) {
        'id ---> sessionControls'

        if ($pol.sessionControls.applicationEnforcedRestrictions) {
            'sessionControls --> applicationEnforcedRestrictions'
            "applicationEnforcedRestrictions --> applicationEnforcedRestrictionsIsEnabled[isEnabled: $($pol.sessionControls.applicationEnforcedRestrictions.isEnabled)]"
        }

        if ($pol.sessionControls.cloudAppSecurity) {
            'sessionControls --> cloudAppSecurity'
            "cloudAppSecurity --> cloudAppSecurityTypeIsEnabled[isEnabled: $($pol.sessionControls.cloudAppSecurity.isEnabled)]"
            "cloudAppSecurity --> cloudAppSecurityType[cloudAppSecurityType: $($pol.sessionControls.cloudAppSecurity.cloudAppSecurityType)]"
        }

        if ($pol.sessionControls.persistentBrowser) {
            'sessionControls --> persistentBrowser'
            "persistentBrowser --> persistentBrowserIsEnabled[isEnabled: $($pol.sessionControls.persistentBrowser.isEnabled)]"
            "persistentBrowser --> persistentBrowserSessionMode[mode: $($pol.sessionControls.persistentBrowser.mode)]"
        }

        if ($pol.sessionControls.signInFrequency) {
            'sessionControls --> signInFrequency'
            "signInFrequency --> signInFrequencyIsEnabled[isEnabled: $($pol.sessionControls.signInFrequency.isEnabled)]"
            "signInFrequency --> signinFrequencyType[type: $($pol.sessionControls.signInFrequency.type)]"
            "signInFrequency --> signinFrequencyValue[value: $($pol.sessionControls.signInFrequency.value)]"
        }
    }

    if ($asHTML) {
        @'
</div>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({startOnLoad:true});</script>
</body>
</html>
'@
    
    }
    elseif ($asMarkdown) {
        @'
:::
'@

    }
}
#endregion