MiniGraph.psm1
function Assert-GraphConnection { <# .SYNOPSIS Asserts a valid graph connection has been established. .DESCRIPTION Asserts a valid graph connection has been established. .PARAMETER Cmdlet The $PSCmdlet variable of the calling command. .EXAMPLE PS C:\> Assert-GraphConnection -Cmdlet $PSCmdlet Asserts a valid graph connection has been established. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Cmdlet ) process { if ($script:token) { return } $exception = [System.InvalidOperationException]::new('Not yet connected to MSGraph. Use Connect-Graph* to establish a connection!') $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, "NotConnected", 'InvalidOperation', $null) $Cmdlet.ThrowTerminatingError($errorRecord) } } function ConvertTo-Base64 { <# .SYNOPSIS Converts input string to base 64. .DESCRIPTION Converts input string to base 64. .PARAMETER Text The text to encode. .PARAMETER Encoding The encoding of the input text. .EXAMPLE PS C:\> "Hello World" | ConvertTo-Base64 Converts the string "Hello World" to base 64. #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string[]] $Text, [System.Text.Encoding] $Encoding = [System.Text.Encoding]::UTF8 ) process { foreach ($entry in $Text) { $bytes = $Encoding.GetBytes($entry) [Convert]::ToBase64String($bytes) } } } function ConvertTo-SignedString { <# .SYNOPSIS Signs a string. .DESCRIPTION Signs a string. Used for certificate authentication. .PARAMETER Text The text to sign. .PARAMETER Certificate The certificate to sign with. Must have private key. .PARAMETER Padding The padding mechanism to use while signing. Defaults to "Pkcs1" .PARAMETER Algorithm The signing algorithm to use. Defaults to "SHA256" .PARAMETER Encoding Encoding of the source text. Defaults to UTF8 .EXAMPLE PS C:\> $token | ConvertTo-SignedString -Certificate $cert Signs the text stored in $token with the certificate stored in $cert #> [OutputType([string])] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string[]] $Text, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [Security.Cryptography.RSASignaturePadding] $Padding = [Security.Cryptography.RSASignaturePadding]::Pkcs1, [Security.Cryptography.HashAlgorithmName] $Algorithm = [Security.Cryptography.HashAlgorithmName]::SHA256, [System.Text.Encoding] $Encoding = [System.Text.Encoding]::UTF8 ) process { foreach ($entry in $Text) { $inBytes = $Encoding.GetBytes($entry) $outBytes = $Certificate.PrivateKey.SignData($inBytes, $Algorithm, $Padding) [convert]::ToBase64String($outBytes) } } } function Connect-GraphCertificate { <# .SYNOPSIS Connect to graph as an application using a certificate .DESCRIPTION Connect to graph as an application using a certificate .PARAMETER Certificate The certificate to use for authentication. .PARAMETER TenantID The Guid of the tenant to connect to. .PARAMETER ClientID The ClientID / ApplicationID of the application to connect as. .EXAMPLE PS C:\> $cert = Get-Item -Path 'Cert:\CurrentUser\My\082D5CB4BA31EED7E2E522B39992E34871C92BF5' PS C:\> Connect-GraphCertificate -TenantID '0639f07d-76e1-49cb-82ac-abcdefabcdefa' -ClientID '0639f07d-76e1-49cb-82ac-1234567890123' -Certificate $cert Connect to graph with the specified cert stored in the current user's certificate store. .LINK https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials #> [CmdletBinding()] param ( [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [string] $TenantID, [string] $ClientID ) $jwtHeader = @{ alg = "RS256" typ = "JWT" x5t = [Convert]::ToBase64String($Certificate.GetCertHash()) -replace '\+', '-' -replace '/', '_' -replace '=' } $encodedHeader = $jwtHeader | ConvertTo-Json | ConvertTo-Base64 $claims = @{ aud = "https://login.microsoftonline.com/$TenantID/v2.0" exp = ((Get-Date).AddMinutes(5) - (Get-Date -Date '1970.1.1')).TotalSeconds -as [int] iss = $ClientID jti = "$(New-Guid)" nbf = ((Get-Date) - (Get-Date -Date '1970.1.1')).TotalSeconds -as [int] sub = $ClientID } $encodedClaims = $claims | ConvertTo-Json | ConvertTo-Base64 $jwtPreliminary = $encodedHeader, $encodedClaims -join "." $jwtSigned = ($jwtPreliminary | ConvertTo-SignedString -Certificate $Certificate) -replace '\+', '-' -replace '/', '_' -replace '=' $jwt = $jwtPreliminary, $jwtSigned -join '.' $body = @{ client_id = $ClientID client_assertion = $jwt client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' scope = 'https://graph.microsoft.com/.default' grant_type = 'client_credentials' } $header = @{ Authorization = "Bearer $jwt" } $uri = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" try { $script:token = (Invoke-RestMethod -Uri $uri -Method Post -Body $body -Headers $header -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop).access_token } catch { throw } } function Connect-GraphClientSecret { <# .SYNOPSIS Connects using a client secret. .DESCRIPTION Connects using a client secret. .PARAMETER ClientID The ID of the registered app used with this authentication request. .PARAMETER TenantID The ID of the tenant connected to with this authentication request. .PARAMETER ClientSecret The actual secret used for authenticating the request. .PARAMETER Scopes Generally doesn't need to be changed from the default 'https://graph.microsoft.com/.default' .PARAMETER Resource The resource the token grants access to. Generally doesn't need to be changed from the default 'https://graph.microsoft.com/' Only needed when connecting to another service. .EXAMPLE PS C:\> Connect-GraphClientSecret -ClientID '<ClientID>' -TenantID '<TenantID>' -ClientSecret $secret Connects to the specified tenant using the specified client and secret. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $TenantID, [Parameter(Mandatory = $true)] [securestring] $ClientSecret, [string[]] $Scopes = 'https://graph.microsoft.com/.default', [string] $Resource = 'https://graph.microsoft.com/' ) process { $body = @{ client_id = $ClientID client_secret = [PSCredential]::new('NoMatter', $ClientSecret).GetNetworkCredential().Password scope = $Scopes -join " " grant_type = 'client_credentials' resource = $Resource } try { $authResponse = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" -Body $body -ErrorAction Stop } catch { throw } $script:token = $authResponse.access_token } } function Connect-GraphCredential { <# .SYNOPSIS Connect to graph using username and password. .DESCRIPTION Connect to graph using username and password. This logs into graph as a user, not as an application. Only cloud-only accounts can be used for this workflow. Consent to scopes must be granted before using them, as this command cannot show the consent prompt. .PARAMETER Credential Credentials of the user to connect as. .PARAMETER TenantID The Guid of the tenant to connect to. .PARAMETER ClientID The ClientID / ApplicationID of the application to use. .PARAMETER Scopes The permission scopes to request. .EXAMPLE PS C:\> Connect-GraphCredential -Credential max@contoso.com -ClientID $client -TenantID $tenant -Scopes 'user.read','user.readbasic.all' Connect as max@contoso.com with the rights to read user information. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Credential, [Parameter(Mandatory = $true)] [string] $ClientID, [Parameter(Mandatory = $true)] [string] $TenantID, [string[]] $Scopes = 'user.read' ) $request = @{ client_id = $ClientID scope = $Scopes -join " " username = $Credential.UserName password = $Credential.GetNetworkCredential().Password grant_type = 'password' } try { $answer = Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" -Body $request -ErrorAction Stop } catch { throw } $script:token = $answer.access_token } function Invoke-GraphRequest { <# .SYNOPSIS Execute a request against the graph API .DESCRIPTION Execute a request against the graph API .PARAMETER Query The relative graph query with all conditions appended. .PARAMETER Method Which rest method to use. Defaults to GET. .PARAMETER ContentType Which content type to specify. Defaults to "Application/Json" .PARAMETER Body Any body to specify. Must be a hashtable, will be converted to json. .PARAMETER Raw Return the raw response, rather than processing the output. .PARAMETER NoPaging Only return the first set of data, rather than paging through the entire set. .EXAMPLE PS C:\> Invoke-GraphRequest -Query me Returns information about the current user. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Query, [Microsoft.PowerShell.Commands.WebRequestMethod] $Method = 'GET', [string] $ContentType = 'application/json', [Hashtable] $Body, [switch] $Raw, [switch] $NoPaging ) begin { Assert-GraphConnection -Cmdlet $PSCmdlet } process { $parameters = @{ Uri = "$($script:baseEndpoint)/$($Query.TrimStart("/"))" Method = $Method Headers = @{ Authorization = "Bearer $($script:Token)" } ContentType = $ContentType } if ($Body) { $parameters.Body = $Body | ConvertTo-Json -Compress -Depth 99 } do { try { $data = Invoke-RestMethod @parameters -ErrorAction Stop } catch { throw } if ($Raw) { $data } elseif ($data.Value) { $data.Value } elseif ($data -and $null -eq $data.Value) { $data } $parameters.Uri = $data.'@odata.nextLink' } until (-not $data.'@odata.nextLink' -or $NoPaging) } } function Set-GraphEndpoint { <# .SYNOPSIS Specify which graph endpoint to use for subsequent requests. .DESCRIPTION Specify which graph endpoint to use for subsequent requests. .PARAMETER Type Which kind of endpoint to use. v1 or beta .PARAMETER Url Specify a custom Url as endpoint. Used to switch to a government cloud. .EXAMPLE PS C:\> Set-GraphEndpoint -Type beta Switch to using the beta graph endpoint #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Default')] [ValidateSet('v1','beta')] [string] $Type, [Parameter(Mandatory = $true, ParameterSetName = 'Url')] [string] $Url ) if ($Type) { switch ($Type) { 'v1' { $script:baseEndpoint = 'https://graph.microsoft.com/v1.0' } 'beta' { $script:baseEndpoint = 'https://graph.microsoft.com/beta' } } } if ($Url) { $script:baseEndpoint = $Url.Trim("/") } } # Graph Token used for connections $script:token = $null # Endpoint used for queries $script:baseEndpoint = 'https://graph.microsoft.com/v1.0' |