Microsoft.Graph.PlusPlus.psm1

using namespace System.Management.Automation
using namespace Microsoft.Graph.PowerShell.Models
using namespace Microsoft.Graph.PowerShell.Authentication

$Global:GraphUri                  =   'https://graph.microsoft.com/v1.0'   #Global: instead of Script: for use in cmdline invoke-graphRequest etc.
$Script:GUIDRegex                 =   '^\{?[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\}?$'
$Script:WellKnownMailFolderRegex  =   '^[/\\]?(' +  (@(
                                            'archive',       'clutter',       'conflicts', 'conversationhistory',
                                            'deleteditems',  'drafts',        'inbox',     'junkemail',
                                            'localfailures', 'msgfolderroot', 'outbox',    'recoverableitemsdeletions',
                                            'scheduled',     'searchfolders', 'sentitems', 'serverfailures',
                                            'syncissues'
                                        ) -join '|' ) + ')[/\\]?$'
$Script:DefaultUserProperties     = @(
                                        'businessPhones',   'displayName',    'givenName',  'id',  'jobTitle', 'mail',
                                        'mobilePhone',      'officeLocation', 'preferredLanguage', 'surname',  'userPrincipalName',
                                        'assignedLicenses', 'department',     'usageLocation',     'userType'
                                    )
$Script:DefaultUsageLocation      =   'GB'
$Script:SkippedSubmodules         = @(    )

#region global helper functions, completer, transformer, and validator attributes for parameters **CLASSES NEED TO BE IN PSM1
class UpperCaseTransformAttribute : ArgumentTransformationAttribute  {
    [object] Transform([System.Management.Automation.EngineIntrinsics]$EngineIntrinsics, [object] $InputData) {
        if ($inputData -is [string]) {return $Inputdata.toUpper()}
        else                         {return ($InputData) }
    }
}

class ValidateCountryAttribute    : ValidateArgumentsAttribute {
    [void]Validate([object]$Argument, [EngineIntrinsics]$EngineIntrinsics)  {
        if ($Argument -notin [cultureInfo]::GetCultures("SpecificCultures").foreach({
                                New-Object -TypeName System.Globalization.RegionInfo -ArgumentList $_.name
                             }).TwoLetterIsoRegionName) {
            Throw [ParameterBindingException]::new("'$Argument' is not an ISO 3166 country Code")
        }
    }
}

class ChannelCompleter            : IArgumentCompleter {
    [string] $GroupID = ''
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''
        if (-not $this.GroupID) {
            $Group = $null
            if ($FakeBoundParameters['Group']) {$Group = $FakeBoundParameters['Group']}
            if ($FakeBoundParameters['Team' ]) {$Group = $FakeBoundParameters['Team']}
            #I do mean = not -eq in the elseif statements.
            elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Team"  -like $_})) {
                $Group = $Global:PSDefaultParameterValues[$key]
            }
            elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Group" -like $_})) {
                $Group = $Global:PSDefaultParameterValues[$key]
            }
            if     ($Group.ID)                       { $this.Groupid = $Group.id}
            elseif ($Group -is [string] -and
                    $Group -match $Script:GUIDRegex) { $this.GroupID = $Group}
            elseif ($Group -is [string])             { $this.GroupID = idfromteam $Group }
        }
        if ($this.groupID -and $this.groupID -match $Script:GUIDRegex) {
            Invoke-GraphRequest "$global:GraphUri/Teams/$($this.groupID)/Channels?$`select=id,displayname" -ValueOnly |
                ForEach-Object {
                    if ($_.displayname  -like "$wordToComplete*") {$_.displayName}
                } | Sort-Object |
                    ForEach-Object {
                        $result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) )
                    }
        }
        return $result
    }
}

class DomainCompleter             : IArgumentCompleter {
    [array]$Domains = @()
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    )
    {
        $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()
        if (-not $this.Domains)  {$this.Domains = Invoke-GraphRequest "$Global:GraphUri/domains?`$select=id" -ValueOnly |
                                                         ForEach-Object id | Sort-Object
        }
        $wildcard          = ('*' + ($wordToComplete  -replace '[''"]','' )+ '*')

        foreach ($d in $this.domains.where({$_ -like $wildcard}))  {$result.Add([System.Management.Automation.CompletionResult]::new($d))}
        return $result
    }
}

class GroupCompleter              : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''

        if ($wordToComplete) {$uri =  $Global:GraphUri +  ("/Groups/?`$filter=startswith(displayName,'{0}') or startswith(mail,'{0}')" -f $wordToComplete)}
        else                 {$uri = "$Global:GraphUri/Groups/?`$Top=20"}

        Invoke-GraphRequest -Uri $uri -ValueOnly | ForEach-Object displayname | Sort-Object | ForEach-Object {
                $result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) )
        }

        return $result
    }
}

class MailFolderCompleter         : IArgumentCompleter {
     [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
         [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
         [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
     ) {
         $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

         #strip quotes from word to complete - replace " or ' with nothing.
         $wordToComplete = $wordToComplete -replace '"|''', ''
         #Where interested in what's before the final / or \
         $params = @{'Select' = 'displayname'}
         $path = ''
         if ($wordToComplete -match '^[/\\]?(\w.*)[/\\].*?$') {
             $params['Name'] = $Matches[1];
             $params['ChildItems'] =$true
             $path   = $Matches[1] + '/'
         }
         if ($FakeBoundParameters['User']) {  $params['User'] = $FakeBoundParameters['User']}
         Get-GraphMailFolder @params | ForEach-Object {
             $p = $path+$_.displayname
             if ($p -like "$wordToComplete*") {
                 $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) )
             }
         }
         return $result
     }
}

class OneDriveFolderCompleter     : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''

        If     ($wordToComplete -notmatch "/.+/" -or
                $wordToComplete -eq "/root:?/" )   {$params =@{folderPath = '/'} }
        elseif ($wordToComplete -Match '^/?root:') {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$',      '/$1:'} } #catch after any leading / and before final /; and sandwich between / and :
        else                                       {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$','/root:/$1:'} } #catch after any leading / and before final /; and sandwich between /root/ and :

        if ($FakeBoundParameters['Drive']) {  $params['Drive'] = $FakeBoundParameters['Drive']}
        #I do mean = no -eq in the next line.
        elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Drive" -like $_})) {
            $params['Drive'] = $Global:PSDefaultParameterValues[$key]
        }
        # #it would be better to order-by at the server, but consumer one drive doesn't support it.
        Get-GraphDrive @params -subFolders -quiet | Sort-Object -Property name | ForEach-Object {
            $P = ($_.parentReference.path -replace "/drive/|/drives/.*?/","" ) + "/" + $_.name
            if ($P -like "*$wordToComplete*") {
                $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) )
            }
        }
        return $result
    }
}

class OneDrivePathCompleter       : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''

        If     ($wordToComplete -notmatch "/.+/" -or
                $wordToComplete -eq "/root:?/" )   {$params =@{folderPath = '/'} }
        elseif ($wordToComplete -Match '^/?root:') {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$',      '/$1:'} } #catch after any leading / and before final /; and sandwich between / and :
        else                                       {$params =@{folderPath = $wordToComplete -replace '^/?(.*)/.*?$','/root:/$1:'} } #catch after any leading / and before final /; and sandwich between /root/ and :

        if ($FakeBoundParameters['Drive']) {  $params['Drive'] = $FakeBoundParameters['Drive']}
        #I do mean = no -eq in the next line.
        elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Drive" -like $_})) {
            $params['Drive'] = $Global:PSDefaultParameterValues[$key]
        }
        # #it would be better to order-by at the server, but consumer one drive doesn't support it.
        Get-GraphDrive -quiet @params | Sort-Object -Property name | ForEach-Object {
            $P = ($_.parentReference.path -replace "/drive/|/drives/.*?/","" ) + "/" + $_.name
            if ($P -like "*$wordToComplete*") {
                $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) )
            }
        }
        return $result
    }
}

class OneNoteSectionCompleter     : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''
        $values = @()
        if ($FakeBoundParameters['Notebook'] -and $FakeBoundParameters['Notebook'].Sections  ) {$values=$FakeBoundParameters['Notebook'].Sections.DisplayName}
        #I do mean = no -eq in the next line.
        elseif ($key = $Global:PSDefaultParameterValues.Keys.where({"$CommandName`:Notebook" -like $_})) {
            $values = $Global:PSDefaultParameterValues[$key].Sections.DisplayName
        }
        foreach ($p in $values) {
            if ($P -like "$wordToComplete*" -and $p -match '^\w+$') {
                $result.Add([System.Management.Automation.CompletionResult]::new($p, $p, ([CompletionResultType]::ParameterValue) , $p) )
            }
            elseif ($P -like "$wordToComplete*") {
                $result.Add([System.Management.Automation.CompletionResult]::new("'$p'", $p, ([CompletionResultType]::ParameterValue) , $p) )
            }
        }
        return $result
    }
}

class SkuCompleter                : IArgumentCompleter {
    [array]$skus = @()
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    )
    {
        $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()
        if (-not $this.skus)  {$this.skus = Get-GraphSKU }
        $wildcard          = ("*" + ($wordToComplete  -replace "['""]",'' )+ "*")
        $this.skus.where({$_.skuPartNumber -like $wildcard}).skuPartNumber |
            Sort-Object | ForEach-Object {$result.Add([System.Management.Automation.CompletionResult]::new($_))}
        return $result
    }
}

class SkuPlanCompleter            : IArgumentCompleter {
    [array]$skus = @()
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    )
    {
        $result = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()
        if (-not $this.skus)  {$this.skus = Get-GraphSKU }
        $wildcard          = ("*" + ($wordToComplete  -replace "['""]",'' )+ "*")
        if ($FakeBoundParameters['SKUID']) {
            $selectedSkus  = $this.skus.where({$_.skuID -in $FakeBoundParameters['SKUID'] -or $_.skuPartNumber -in $FakeBoundParameters['SKUID'] })
        }
        else {
            $selectedSkus = $this.skus
        }
        $selectedSkus.ServicePlans.where({$_.ServicePlanName -like $wildcard}).ServicePlanName |
            Sort-Object | ForEach-Object {$result.Add([System.Management.Automation.CompletionResult]::new($_))}
        return $result
    }
}

class RoleCompleter               : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        if (-not $wordToComplete) {$wordToComplete = '*'}
        else                      {$wordToComplete = "$wordToComplete*" -replace '"|''', '' }
        Invoke-GraphRequest  -Uri "$Global:GraphUri/directoryroles?`$select=displayname" -ValueOnly |
            Where-Object displayname -like $wordToComplete | Sort-Object -Property displayname | ForEach-Object {
                $result.Add([System.Management.Automation.CompletionResult]::new("'$($_.displayname)'", $_.displayname, ([CompletionResultType]::ParameterValue) , $_.displayname) )
        }
        return $result
    }
}

class TeamCompleter               : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''

        if ($wordToComplete) {$uri = "$Global:GraphUri/groups?`$select=id,resourceProvisioningOptions,displayname&`$filter=startswith(displayname,'{0}')" -f $wordToComplete}
        else                 {$uri = "$Global:GraphUri/groups?`$select=id,resourceProvisioningOptions,displayname"}
        #had "ResourceProvisioningOptions eq 'team' and " in the filter but it removed some valid teams so this is just completing groups for now

        Invoke-GraphRequest -Uri $uri -ValueOnly |
            ForEach-Object {if ("Team" -in $_.resourceProvisioningOptions) {$_.displayname}} |
                Sort-Object | ForEach-Object {
                $result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) )
        }
        return $result
    }
}

class UPNCompleter                : IArgumentCompleter {
    [System.Collections.Generic.IEnumerable[CompletionResult]] CompleteArgument(
        [string]$CommandName, [string]$ParameterName, [string]$WordToComplete,
        [Language.CommandAst]$CommandAst, [System.Collections.IDictionary] $FakeBoundParameters
    ) {
        $result =  [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new()

        #strip quotes from word to complete - replace " or ' with nothing
        $wordToComplete = $wordToComplete -replace '"|''', ''
        if ($wordToComplete) {
            Invoke-GraphRequest -ValueOnly -headers @{'ConsistencyLevel'='eventual'} -uri "$Global:GraphUri/users?`$filter=startswith(userprincipalname,'$wordToComplete')&top=10&select=userprincipalName" |
                ForEach-Object userPrincipalName | sort-object | ForEach-Object {$result.Add([System.Management.Automation.CompletionResult]::new("'$_'", $_, ([CompletionResultType]::ParameterValue) , $_) )}
        }
        return $result
    }
}

function FilterString {
    param (
        [validatescript({
            if ($_ -is [string] -and $_ -match '\*.*\*|^\*$') {throw [ParameterBindingException]::new("Wildcard cannot be '*something*' or just '*'")} else {$true}
        })]
        [parameter(position=0,mandatory=$true)]
        [string]$SearchTerm ,
        [parameter(position=1)]
        $ExtraFields = @(),
        [switch]$ToLower
    )
    if ($toLower) {$SearchTerm = $SearchTerm.ToLower() }
    #Replace ' with '' - ensure we don't turn '' into '''' !
    $SearchTerm = $SearchTerm -replace "(?<!')'(?!')" ,"''"
    #validation blocked "* and *something*" so we have no *, * at the start, in the middle, or at the end
    if     ($SearchTerm -notmatch '\*')         {$filterStrings = ,              "displayName eq '$SearchTerm'"     }
    elseif ($SearchTerm -match   '^\*(.+)')     {$filterStrings = ,     "endswith(displayName,'$($Matches[1])')"    }
    elseif ($SearchTerm -match   '(.+)\*$')     {$filterStrings = ,   "startswith(displayName,'$($Matches[1])')"    }
    elseif ($SearchTerm -match  '^(.+)\*(.+)$') {$filterStrings = , ("(startswith(displayName,'$($Matches[1])')" +
                                                                " and endswith(displayName,'$($Matches[2])'))"  )}
    if ($ToLower) {$filterStrings[0] = $filterStrings[0] -replace 'displayName' , 'toLower(displayName)'}

    foreach ($f in $ExtraFields) {$filterStrings += $filterStrings[0]   -replace 'displayName',$f }
    $filterStrings -join ' or '
}
#endregion

#region load the bulk of the module
. "$PSScriptRoot\Authentication.ps1"

#Submodules which need the class and/or private functions from the SDK module.
$ImportCmds = [ordered]@{
  'PersonalContacts'             = @()
  'Users'                        = @('Remove-MgUser_Delete','New-MgUserTodoList_CreateExpanded1','New-MgUserTodoListTask_CreateExpanded1', 'Remove-MgUserTodoList_Delete1',
                                     'Remove-MgUserTodoListTask_Delete1', 'Update-MgUserTodoListTask_UpdateExpanded1') #'Get-MgUser_List1' ,
  'Identity.DirectoryManagement' = @('Get-MgDomain_Get1', 'Get-MgDomain_List1',  'Get-MgDomainNameReference_List1', # 'Get-MgDomainNameerenceByRef_List1', Get-MgDomainNameReference_List1
                                     'Get-MgDomainServiceConfigurationRecord_List1' , 'Get-MgDomainVerificationDnsRecord_List1',
                                     'Get-MgOrganization_List1', 'Get-MgSubscribedSku_Get1', 'Get-MgSubscribedSku_List1')
  'Users.Functions'              = @()
  'Users.Actions'                = @()
  'Identity.SignIns'             = @()
  'Reports'                      = @()
  'Applications'                 = @()
}
foreach ($subModule in $ImportCmds.keys) {
    $result = $null
    if (Test-path     (Join-Path $PSScriptRoot -ChildPath "Microsoft.Graph.$subModule.private.dll")) {
        $result = Import-Module -Scope Local (Join-Path $PSScriptRoot -ChildPath "Microsoft.Graph.$subModule.private.dll") -Cmdlet $ImportCmds[$subModule] -PassThru
    }
    # I do mean get module and assign it to module and if it works then... not "$module -eq"
    elseif ($module = Get-Module -ListAvailable "Microsoft.Graph.$submodule" | Sort-Object -Property Version | Select-Object -Last 1) {
        $result = Import-Module -Scope Local (Join-Path (Split-Path $module.Path) -ChildPath "bin\Microsoft.Graph.$submodule.private.dll") -Cmdlet $ImportCmds[$subModule]  -PassThru
    }
    else {
        Write-Verbose "Microsoft.Graph.$subModule.private.dll not found $subModule won't be loaded "
        $Script:SkippedSubmodules += $subModule
    }
    if ($result) {
        .  "$PSScriptRoot\$subModule.ps1"
        foreach ($cmd in $ImportCmds[$subModule]) { $c = Get-Command $cmd; $c.set_visibility('Private')  }
    }
}

if ($Script:SkippedSubmodules -contains 'Users') {
     Write-Verbose "Groups, Notes, OneDrive, Planner, and Sharepoint require the Microsoft.Graph.users module, or Microsoft.Graph.Users.private.dll in the module directory."
     $Script:SkippedSubmodules += @('Groups', 'Notes', 'OneDrive', 'Planner', 'Sharepoint')
}
else { #These submodules will work with just the users module.
    . "$PSScriptRoot\Groups.ps1"
    . "$PSScriptRoot\Notes.ps1"
    . "$PSScriptRoot\OneDrive.ps1"
    . "$PSScriptRoot\Planner.ps1"
    . "$PSScriptRoot\Sharepoint.ps1"
}
if ($Script:SkippedSubmodules) {
      Write-Host -ForegroundColor DarkGray ("Skipped " + ($Script:SkippedSubmodules -join ", ") + " because their Microsoft.Graph module(s) or private.dll file(s) were not found.")
}
#endregion

function Set-GraphOptions {
    <#
        .synopsis
            Sets defaults and the tenant client ID & Client Secret used when logging on without a web dialog
    #>

    [cmdletbinding()]
    param (
        #Your Tennant ID
        $TenantID,
        #Client ID if not using the SDK default of 14d82eec-204b-4c2f-b7e8-296a70dab67e. Must be known to your tennant
        $ClientID,
        #Secret set for the client ID in your $TenantID
        [Alias('Client_Secret,')]
        $ClientSecret,
        #Default Scopes to request
        $DefaultScopes,
        #Allows a saved Refresh Token (e.g. from Show-GraphSession) to be added to the session.
        $RefreshToken,
        #Changes the dafault properties returned by Get-GraphUser and Get-GraphUserList
        [validateSet('accountEnabled', 'ageGroup', 'assignedLicenses', 'assignedPlans', 'businessPhones', 'city',
                    'companyName', 'consentProvidedForMinor', 'country', 'createdDateTime', 'department',
                    'displayName', 'givenName', 'id', 'imAddresses', 'jobTitle', 'legalAgeGroupClassification',
                    'mail', 'mailNickname', 'mobilePhone', 'officeLocation',
                    'onPremisesDomainName', 'onPremisesExtensionAttributes', 'onPremisesImmutableId',
                    'onPremisesLastSyncDateTime', 'onPremisesProvisioningErrors', 'onPremisesSamAccountName',
                    'onPremisesSecurityIdentifier', 'onPremisesSyncEnabled', 'onPremisesUserPrincipalName',
                    'passwordPolicies', 'passwordProfile', 'postalCode', 'preferredDataLocation',
                    'preferredLanguage', 'provisionedPlans', 'proxyAddresses', 'state', 'streetAddress',
                    'surname', 'usageLocation', 'userPrincipalName', 'userType')]
        [string[]]$DefaultUserProperties,

        #Changes the default two letter (ISO 3166) country code - for new users so they can be assigned licenses. Examples include: 'US', 'JP', and 'GB'
        [ValidateNotNullOrEmpty()]
        [string]$DefaultUsageLocation
    )

    if     ($TenantID)              {
        if ($TenantID -notmatch $GUIDRegex) {Write-Warning 'TenantID should be a GUID'  ; break }
        else {$Script:TenantID           = $TenantID}
    }
    if     ($ClientID)              {
        if ($Clientid -notmatch $GUIDRegex) {Write-Warning 'ClientID should be a GUID'  ; break}
        else {$Script:ClientID  = $ClientID}
    }
    if     ($ClientSecret)          {
        if     ($ClientSecret -is [string]) {
               $Script:ClientSecret      = $ClientSecret
        }
        elseif ($ClientSecret -is [securestring]) {
               $Script:ClientSecret =  (new-object pscredential -ArgumentList "NoName", $ClientSecret).GetNetworkCredential().Password
        }
        else  {Write-Warning 'ClientSecret should be a string or preferably a securestring'  ; break}
    }
    if     ($Script:TenantID)       {Write-Verbose "TenantID: '$Script:TenantID' , ClientID: '$Script:ClientID'"}
    if     ($DefaultScopes)         {$Script:DefaultGraphScopes     = $DefaultScopes}
    Write-Verbose ('Scopes: ' + ($Script:DefaultGraphScopes -join ', '))

    #it would be nice to the use the country validator but this goes wrong when reloading the module and calling something when everything is happening in the PSM1 file.
    if     ($DefaultUsageLocation -and -not [cultureInfo]::GetCultures("SpecificCultures").where({$_.name -match "$DefaultUsageLocation$"})) {
           Write-Warning 'DefaultUsageLocation should be an ISO 2 letter country code like GB, US or JP'  ; break
    }
    elseif ($DefaultUsageLocation ) {$Script:DefaultUsageLocation   = $DefaultUsageLocation.ToUpper() }
    if     ($DefaultUserProperties) {$Script:DefaultUserProperties  = $DefaultUserProperties}
    if     ($RefreshToken)          {$Script:RefreshToken           = $RefreshToken }
}

#call a script with calls to Set-GraphOptions
if     ($env:GraphSettingsPath )                       {. $env:GraphSettingsPath}
elseif (Test-Path "$PSScriptRoot\Microsoft.Graph.PlusPlus.settings.ps1") {. "$PSScriptRoot\Microsoft.Graph.PlusPlus.settings.ps1"}

if      ($null -eq [Microsoft.Graph.PowerShell.Authentication.GraphSession]::instance.AuthContext) {Write-Host  "Ready for Connect-Graph."}
elseif ([Microsoft.Graph.PowerShell.Authentication.GraphSession]::instance.AuthContext.AppName -and -not [Microsoft.Graph.PowerShell.Authentication.GraphSession]::instance.AuthContext.Account) {
      Write-Host ("Already logged on as the app '$([Microsoft.Graph.PowerShell.Authentication.GraphSession]::instance.AuthContext.AppName)'." )}
else {Write-Host ("Already logged on as '$([Microsoft.Graph.PowerShell.Authentication.GraphSession]::instance.AuthContext.Account)'." )}