KnowIT.MSGraph.psm1
#region === Source functions === ### Source file: 'Add-EntraDirectoryRoleMember.ps1' ### function Add-EntraDirectoryRoleMember { [Alias('Add-AzAdDirectoryRoleMember')] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$PrincipalId, [Parameter(Mandatory, ParameterSetName = 'RoleId')] [ValidateNotNullOrEmpty()] [string]$Id, [Parameter(Mandatory, ParameterSetName = 'RoleTemplateId')] [ValidateNotNullOrEmpty()] [string]$RoleTemplateId, [Parameter(Mandatory, ParameterSetName = 'RoleName')] [Alias('RoleName')] [string]$Name ) try { Update-CallerPreference [void]$PSBoundParameters.Remove('PrincipalId') $role = Get-EntraDirectoryRole @PSBoundParameters if(!$role) { throw "No esta definido el $($PSCmdlet.ParameterSetName) [$($Id + $RoleTemplateId + $Name)] en el Directorio '$((Get-AzContext).Tenant.Name)'" } $request = @{ principalId = $PrincipalId roleDefinitionId = $role.templateId directoryScopeId = '/' } Invoke-MsGraph 'roleManagement/directory/roleAssignments' -Method POST -Body $request -ErrorAction Stop } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'ConvertFrom-MsGraphResponse.ps1' ### filter ConvertFrom-MsGraphResponse { param( [Parameter(Mandatory, ValueFromPipeline)] [PSTypeNameAttribute('KnowIT.MsGraph.Response')] $InputObject ) $statusCode = $InputObject.StatusCode if($InputObject.Success) { return $InputObject.Value } if($statusCode -eq 404) { Write-Verbose "Response MsGraph: No se obtuvo ningun registro" return } Write-Error -ErrorRecord ([Management.Automation.ErrorRecord]::new( [Exception]$InputObject.Error.message, $InputObject.Error.code, [Management.Automation.ErrorCategory]::InvalidResult, $InputObject)) } ### Source file: 'Get-EntraAppExpiringCreds.ps1' ### function Get-EntraAppExpiringCreds { param( [int]$Days = 15 ) try { Update-CallerPreference $dateLimit = (Get-Date).AddDays($Days) Invoke-MsGraph 'applications?$select=id,appId,displayName,passwordCredentials,keyCredentials' -ErrorAction Stop | Map-Object { $app = $_ foreach($credential in $app.passwordCredentials + $app.keyCredentials) { if($credential -and $credential.endDateTime -le $dateLimit) { [PSCustomObject]@{ Name = $app.displayName ApplicationId = $app.appId ObjectId = $app.id Credential = $credential.customKeyIdentifier ? $credential.displayName : 'Password' Expiration = $credential.endDateTime } } } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Get-EntraApplication.ps1' ### function Get-EntraApplication { param ( [Parameter(Position = 0, ParameterSetName = 'ById')] [ValidateNotNullOrEmpty()] [Alias('ObjectId')] [string]$Id, [Parameter(Mandatory, ParameterSetName = 'ByApplicationId')] [ValidateNotNullOrEmpty()] [string]$ApplicationId, [Parameter(Mandatory, ParameterSetName = 'ByUniqueName')] [ValidateNotNullOrEmpty()] [string]$UniqueName, [Parameter(Mandatory, ParameterSetName = 'ByServicePrincipal')] [ValidateNotNullOrEmpty()] [string]$ServicePrincipalId ) try { Update-CallerPreference Write-Verbose "Procesando parametros: '$($PSCmdlet.ParameterSetName)'" switch ($PSCmdlet.ParameterSetName) { 'ById' { Invoke-MsGraph "applications/$Id" break } 'ByApplicationId' { Invoke-MsGraph "applications(appId='$ApplicationId')" break } 'ByUniqueName' { Invoke-MsGraph "applications(uniqueName='$UniqueName')" break } 'ByServicePrincipal' { $sp = Invoke-MsGraph "servicePrincipals/$ServicePrincipalId" if($sp) { Invoke-MsGraph "applications(appId='$($sp.appId)')" } break } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Get-EntraDirectoryRole.ps1' ### function Get-EntraDirectoryRole { [CmdletBinding()] [Alias('Get-AzAdDirectoryRole')] param( [Parameter(ParameterSetName = 'RoleId')] [ValidateNotNullOrEmpty()] [Alias('RoleId')] [string]$Id, [Parameter(Mandatory, ParameterSetName = 'RoleTemplateId')] [ValidateNotNullOrEmpty()] [string]$RoleTemplateId, [Parameter(Mandatory, Position = 0, ParameterSetName = 'RoleName')] [ValidateNotNullOrEmpty()] [Alias('DisplayName')] [string]$Name ) try { Update-CallerPreference Write-Verbose "Procesando parametros: '$($PSCmdlet.ParameterSetName)'" switch ($PSCmdlet.ParameterSetName) { 'RoleId' { $path = "directoryRoles/$Id" break } 'RoleTemplateId' { $path = "directoryRoles(roleTemplateId='$RoleTemplateId')" break } 'RoleName' { $path = "directoryRoles?`$filter=displayName eq '$Name'" break } } Invoke-MsGraph $path | Map-Object { [PSCustomObject]@{ id = $_.id templateId = $_.roleTemplateId name = $_.displayName description = $_.description } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Get-EntraDirectoryRoleMembers.ps1' ### function Get-EntraDirectoryRoleMembers { [CmdletBinding(DefaultParameterSetName = 'RoleName')] [Alias('Get-AzAdDirectoryRoleMembers')] param( [Parameter(Mandatory, ParameterSetName = 'RoleId')] [ValidateNotNullOrEmpty()] [Alias('RoleId')] [string]$Id, [Parameter(Mandatory, ParameterSetName = 'RoleTemplateId')] [ValidateNotNullOrEmpty()] [string]$RoleTemplateId, [Parameter(Mandatory, Position = 0, ParameterSetName = 'RoleName')] [ValidateNotNullOrEmpty()] [Alias('DisplayName')] [string]$Name ) try { Update-CallerPreference $roles = Get-EntraDirectoryRole @PSBoundParameters foreach($role in $roles) { Invoke-MsGraph "roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '$($role.TemplateId)'&`$expand=principal" | Map-Object { [PSCustomObject]@{ role = $role.name principalId = $_.principal.id principalName = $_.principal.'@odata.type' -eq '#microsoft.graph.user' ? $_.principal.userPrincipalName : "appId=$($_.principal.appId)" displayName = $_.principal.displayName roleId = $role.id roleTemplateId = $role.templateId assignmentId = $_.id enabled = $_.principal.accountEnabled principalType = $_.principal.'@odata.type'.Split('.')[-1] #-replace "^(.)", { $_.Groups[1].Value.ToUpper() } #Pricipal = $_.principal } } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Get-EntraGroup.ps1' ### function Get-EntraGroup { [CmdletBinding()] param ( [Parameter(ParameterSetName = 'ById')] [ValidateNotNullOrEmpty()] [Alias('ObjectId')] [string]$Id, [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByDisplayName')] [ValidateNotNull()] [string]$DisplayName ) try { Update-CallerPreference Write-Verbose "Procesando parametros: '$($PSCmdlet.ParameterSetName)'" switch ($PSCmdlet.ParameterSetName) { 'ById' { Invoke-MsGraph "groups/$Id" } 'ByDisplayName' { Invoke-MsGraph "groups?`$filter=displayName eq '$DisplayName'" } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Get-EntraServicePrincipal.ps1' ### function Get-EntraServicePrincipal { param ( [Parameter(Position = 0, ParameterSetName = 'ById')] [ValidateNotNullOrEmpty()] [Alias('ObjectId')] [string]$Id, [Parameter(Mandatory, ParameterSetName = 'ByApplicationId')] [ValidateNotNullOrEmpty()] [string]$ApplicationId ) try { Update-CallerPreference Write-Verbose "Procesando parametros: '$($PSCmdlet.ParameterSetName)'" switch ($PSCmdlet.ParameterSetName) { 'ById' { Invoke-MsGraph "servicePrincipals/$Id" break; } 'ByApplicationId' { Invoke-MsGraph "servicePrincipals(appId='$ApplicationId')" break; } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Get-EntraUser.ps1' ### function Get-EntraUser { [CmdletBinding()] param ( [Parameter(ParameterSetName = 'ById')] [Alias('ObjectId')] [string]$Id, [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByUPN')] [string]$UserPrincipalName ) try { Update-CallerPreference Write-Verbose "Procesando parametros: '$($PSCmdlet.ParameterSetName)'" switch ($PSCmdlet.ParameterSetName) { 'ById' { Invoke-MsGraph "users/$Id" } 'ByUPN' { Invoke-MsGraph "users?`$filter=userPrincipalName eq '$UserPrincipalName'" } } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Grant-EntraAppConsent.ps1' ### function Grant-EntraAppConsent { param( [Parameter(Mandatory)] [string]$ClientId, [Parameter(Mandatory)] [ValidateCount(1, 100)] [string]$Scopes, [Alias('API')] [string]$ApplicationId = '00000003-0000-0000-c000-000000000000', # default Microsoft Graph AppId, [Parameter(Mandatory, ParameterSetName = 'ByUser')] [string]$UserPrincipalName, [Parameter(Mandatory, ParameterSetName = 'AllUsers')] [switch]$AllUsers ) try { Update-CallerPreference $clientSp = GetAppServicePrincipal -ApplicationId $ClientId $applicationSp = GetAppServicePrincipal -ApplicationId $ApplicationId $params = @{ clientId = $clientSp.Id resourceId = $applicationSp.Id scope = $Scopes } if($PSCmdlet.ParameterSetName -eq 'AllUsers') { $params.consentType = 'AllPrincipals' Write-Verbose "Concede permisos [$Scopes] de '$($applicationSp.displayName)' a todos los usuarios de la aplicación '$($clientSp.displayName)'" } elseif($PSCmdlet.ParameterSetName -eq 'ByUser') { $principalId = (Get-EntraUser -UserPrincipalName $UserPrincipalName).Id if(!$principalId) { $azureCtx = Get-AzContext throw "No se encontro el usuario [$UserPrincipalName] en el directorio '$($azureCtx.Tenant.Name)'" } $params.consentType = 'Principal' $params.principalId = $principalId Write-Verbose "Concede permisos [$Scopes] de '$($applicationSp.displayName)' al usuario $UserPrincipalName en la aplicación '$($clientSp.displayName)'" } Invoke-MsGraph 'oauth2PermissionGrants' -Method Post -Body $params -ErrorAction Stop } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Grant-EntraAppRoleAssignment.ps1' ### function Grant-EntraAppRoleAssignment { param ( [Parameter(Mandatory)] [string]$ApplicationId, [Parameter(Mandatory)] [ValidateCount(1, 100)] [string[]]$Roles, [Parameter(Mandatory, ParameterSetName = 'User')] [string]$UserPrincipalName, [Parameter(Mandatory, ParameterSetName = 'Group')] [string]$GroupName, [Parameter(Mandatory, ParameterSetName = 'App')] [string]$ClientId ) try { Update-CallerPreference $applicationSp = GetAppServicePrincipal -ApplicationId $ApplicationId -Properties 'appRoles' $principalId = switch ($PSCmdlet.ParameterSetName) { 'App' { (GetAppServicePrincipal -ApplicationId $ClientId).id } 'User' { (Get-EntraUser -UserPrincipalName $UserPrincipalName).id } 'Group' { $group = Get-EntraGroup -DisplayName $GroupName if($group -and -not $group.securityEnabled) { throw "El grupo [$GroupName] no existe o no tiene habilitada la propiedad 'SecurityEnabled'" } $group.id } } if(!$principalId) { $azureCtx = Get-AzContext throw "No se encontro el objeto [$($ClientId + $UserPrincipalName + $GroupName)] en el directorio '$($azureCtx.Tenant.Name)'" } $memberType = $PSCmdlet.ParameterSetName -eq 'App' ? 'Application' : 'User' $appRoles = $applicationSp.appRoles.Where( { $_.value -in $Roles -and $_.allowedMemberTypes -contains $memberType }) if(!$appRoles) { throw "La Aplicacion [$($applicationSp.displayName)] no contiene ninguno de los roles solicitados" } $path = 'servicePrincipals/{0}/appRoleAssignedTo' -f $applicationSp.id $params = @{ principalId = $principalId resourceId = $applicationSp.id appRoleId = $null } Write-Verbose "Asignando los roles [$($appRoles.value -join ',')] de la aplicacion '$($applicationSp.displayName)' al PrincipalId: $principalId" foreach($roleId in $appRoles.id) { $params.appRoleId = $roleId Invoke-MsGraph $path -Method Post -Body $params -ErrorAction Stop } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Invoke-MsGraph.ps1' ### function Invoke-MsGraph { [CmdletBinding()] [Alias('msgr')] param( [Parameter(Mandatory, Position = 0)] [string]$Path, [ValidateSet('GET', 'POST', 'PATCH', 'PUT', 'DELETE')] [string]$Method = 'GET', [object]$Body, [hashtable]$Headers = @{}, [switch]$DetailResponse, [switch]$Beta ) try { Update-CallerPreference $PSCmdlet -Skip Verbose $token = GetGraphToken $Headers.Authorization = "Bearer $token" $path = $Path.TrimStart('/') $version = $Beta ? 'beta' : 'v1.0' $restParams = @{ Method = $Method Uri = "https://graph.microsoft.com/$version/$path" Headers = $Headers Body = $Body ? (ConvertTo-Json $Body -Depth 4) : $null ContentType = 'application/json' Verbose = $false } Write-Verbose "Request MsGraph: $($restParams.Method.ToUpper()) /$version/$path" if($Body) { Write-Debug "Body: $($restParams.Body)" } $result = Invoke-RestMethod @restParams -SkipHttpErrorCheck -StatusCodeVariable statusCode if(!$result.error) { Write-Verbose "Response MsGraph: [$statusCode] $([Net.HttpStatusCode]$statusCode)" $response = [PSCustomObject]@{ PSTypeName = 'KnowIT.MsGraph.Response' Success = $true StatusCode = $statusCode Value = $result.Value ?? $result } } else { Write-Debug 'Procesando error en la respuesta del API...' $response = [PSCustomObject]@{ PSTypeName = 'KnowIT.MsGraph.Response' Success = $false StatusCode = $statusCode Error = [ordered]@{ code = $result.error.innerError?.code ?? $result.error.code message = $result.error.innerError?.message ?? $result.error.message details = $result.error.innerError | Select-Object -Exclude 'code', 'message', 'details' additionalErrors = ($result.error.details ?? @()) + ($result.error.innerError.details ?? @()) } } Write-Verbose "Response MsGraph: [$statusCode] $($response.Error.code) - $($response.Error.message)" } if($DetailResponse) { $response } else { $response | ConvertFrom-MsGraphResponse } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Register-EntraApplication.ps1' ### function Register-EntraApplication { param ( [Parameter(Mandatory, Position = 0)] [ValidatePattern('^[a-z]+(-[a-z0-9]+)*$')] [string]$Name, [string]$DisplayName = $Name, [hashtable]$AppProperties = @{}, [switch]$WithServicePrincipal ) try { Update-CallerPreference $AppProperties.uniqueName = $Name.ToLower() $AppProperties.displayName = $DisplayName $graphParams = @{ Method = 'PATCH' Path = "applications(uniqueName='$Name')" Body = $AppProperties Headers = @{ Prefer = 'create-if-missing' } } $graphResult = Invoke-MsGraph @graphParams -DetailResponse $app = switch ($graphResult.StatusCode) { 201 { Write-Verbose "Aplicacion [$Name] creada exitosamente (AppId = '$($graphResult.Value.appId)')" $graphResult.Value break } 204 { Write-Verbose "Aplicacion [$Name] existente. Actualizada de manera exitosa!" Invoke-MsGraph $graphParams.Path break } 400 { foreach($e in $graphResult.Error.additionalErrors) { if($e.code -eq 'ObjectConflict' -and $e.target -eq 'uniqueName') { #TODO: Recuperar? Eliminar permanentemente throw 'La aplicacion ha sido eliminada??' } } $graphResult | ConvertFrom-MsGraphResponse } default { # En este caso el resultado solo puede ser no exitoso por lo que se generaria el un error de terminacion $graphResult | ConvertFrom-MsGraphResponse throw # Realmente nunca deberia llegar aqui } } if($WithServicePrincipal){ $sp = RegisterAppServicePrincipal $app.appId $app | Add-Member -MemberType NoteProperty servicePrincipalId -Value $sp.id } return $app } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Register-EntraServicePrincipal.ps1' ### function Register-EntraServicePrincipal { [CmdletBinding()] param ( [Parameter(Mandatory, ParameterSetName = 'AppId')] [string]$ApplicationId, [Parameter(Mandatory, Position = 0, ParameterSetName = 'Name')] [string]$Name, [Parameter(ParameterSetName = 'Name')] [hashtable]$AppProperties = @{} ) try { Update-CallerPreference if($PSCmdlet.ParameterSetName -eq 'Name') { $appName = $Name.Replace('/', '-').ToLower() $app = Register-EntraApplication $appName -DisplayName $Name -AppProperties $AppProperties $ApplicationId = $app.appId } RegisterAppServicePrincipal $ApplicationId } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Remove-EntraDirectoryRoleMember.ps1' ### function Remove-EntraDirectoryRoleMember { [Alias('Remove-AzAdDirectoryRoleMember')] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$PrincipalId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$RoleTemplateId ) try { Update-CallerPreference $roleAssignment = Invoke-MsGraph "roleManagement/directory/roleAssignments?`$select=id&`$filter=principalId eq '$PrincipalId' and roleDefinitionId eq '$RoleTemplateId'" if(!$roleAssignment.id) { throw "El PrincipalId '$PrincipalId' no tiene asignado el rol [$RoleTemplateId]" } Invoke-MsGraph "roleManagement/directory/roleAssignments/$($roleAssignment.id)" -Method DELETE } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Set-EntraAppCredential.ps1' ### function Set-EntraAppCredential { [CmdletBinding()] param( [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByApplicationId')] [ValidateNotNullOrEmpty()] [string]$ApplicationId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [int]$ExpireMonths = 12 ) try { Update-CallerPreference $app = GetApplication $ApplicationId -Properties 'passwordCredentials' $removePath = "applications/$($app.id)/removePassword" $app.passwordCredentials. Where({ $_.displayName -eq $Name }). ForEach({ Invoke-MsGraph $removePath -Body @{ keyId = $_.keyId } -Method POST }) $credential = @{ passwordCredential = @{ displayName = $Name endDateTime = [datetime]::UtcNow.AddMonths($ExpireMonths).ToString('o') } } Write-Verbose "Generando nueva credencial '$Name'" Invoke-MsGraph "applications/$($app.id)/addPassword" -Body $credential -Method POST } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Set-EntraAppFederatedIdentity.ps1' ### function Set-EntraAppFederatedIdentity { param ( [Parameter(Mandatory, Position = 0, ParameterSetName = 'ByApplicationId')] [ValidateNotNullOrEmpty()] [string]$ApplicationId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Issuer, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Subject, [Parameter()] [ValidateNotNullOrEmpty()] [string[]]$Audience = @('api://AzureADTokenExchange') ) try { Update-CallerPreference $app = GetApplication $ApplicationId $path = "applications/$($app.id)/federatedIdentityCredentials(name='$Name')" $body = @{ name = $Name issuer = $Issuer subject = $Subject audiences = $Audience } $null = Invoke-MsGraph $path -Method PATCH -Body $body -Headers @{ Prefer = 'create-if-missing' } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Applications.ps1' ### function GetApplication ([string]$ApplicationId, [string[]]$Properties) { $propList = ('id', 'appId', 'displayName' + $Properties) -join ',' $app = Invoke-MsGraph "applications(appId='$ApplicationId')?`$select=$propList" if(!$app) { $azureCtx = Get-AzContext throw "No se encontro la aplicación [$ApplicationId] en el directorio '$($azureCtx.Tenant.Name)'" } $app } ### Source file: 'KnowIT.ModuleHelpers.ps1' ### function Update-CallerPreference { # https://devblogs.microsoft.com/scripting/weekend-scripter-access-powershell-preference-variables/ param( [ValidateNotNull()] [PSTypeName('System.Management.Automation.PSScriptCmdlet')]$ScriptCmdlet = (Get-Variable PSCmdlet -Scope 1 -ValueOnly), [ValidateSet('ErrorAction', 'Warning', 'Verbose', 'Debug', 'Information', 'Progress', 'Confirm', 'WhatIf')] [string[]]$Skip ) $commonParameters = 'ErrorAction', 'Warning', 'Verbose', 'Debug', 'Information', 'Progress', 'Confirm', 'WhatIf' $invocation = $ScriptCmdlet.MyInvocation $commandDebug = $invocation.BoundParameters.ContainsKey('Debug') Write-Debug "Updating [$($invocation.MyCommand)] Preference variables:" -Debug:$commandDebug foreach($p in $commonParameters) { if($invocation.BoundParameters.ContainsKey($p)) { continue } $var = "${p}Preference" if($p -eq 'ErrorAction') { $val = 'Stop' $scope = 'Forced' } elseif($p -in $Skip) { $val = Get-Variable -Scope Global -Name $var -ValueOnly $scope = 'Global' } else { $val = $ScriptCmdlet.GetVariableValue($var) $scope = 'Caller' } Write-Debug " (From $scope scope) $var = $val " -Debug:$commandDebug Set-Variable -Scope 1 -Name $var -Value $val } } function Map-Object { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '', Justification = 'Internal functions')] param([scriptblock]$ScriptBlock) begin { $code = "& { process { $ScriptBlock } }" $pipeline = [scriptblock]::Create($code).GetSteppablePipeline() $pipeline.Begin($true) } process { $pipeline.Process($_) } end { $pipeline.End() } } ### Source file: 'ServicePrincipals.ps1' ### function GetAppServicePrincipal ([string]$ApplicationId, [string[]]$Properties) { $propList = ('id', 'appId', 'displayName' + $Properties) -join ',' $servicePrincipal = Invoke-MsGraph "servicePrincipals(appId='$ApplicationId')?`$select=$propList" if(!$servicePrincipal) { $azureCtx = Get-AzContext throw "No se encontro el 'Service Principal' asociado a la aplicación [$ApplicationId] en el directorio '$($azureCtx.Tenant.Name)'" } $servicePrincipal } function RegisterAppServicePrincipal ([string]$ApplicationId) { $ErrorActionPreference = 'Stop' $sp = Invoke-MsGraph "servicePrincipals(appId='$ApplicationId')" if(!$sp) { $azureCtx = Get-AzContext Write-Verbose "Registrando nuevo Service Principal para la aplicación [$ApplicationId] en directorio '$($azureCtx.Tenant.Name)'..." $sp = Invoke-MsGraph 'servicePrincipals' -Method POST -Body @{ appId = $ApplicationId } } return $sp } ### Source file: 'Tokens.ps1' ### function GetGraphToken { $token = Get-AzAccessToken -ResourceUrl 'https://graph.microsoft.com' -AsSecureString -Debug:$false ConvertFrom-SecureString $token.Token -AsPlainText } #endregion |