GraphApiRequests.psm1
### --- PUBLIC FUNCTIONS --- ### #Region - Get-GraphCertToken.ps1 Function Get-GraphCertToken { <# .SYNOPSIS Function for generating token from certificate .DESCRIPTION This function uses the Certificate private part instead of Secret for getting the Token .NOTES Requires: - App ID - Tenatn ID - Certificate Path .EXAMPLE Get-GraphCertToken -AppId 5265b837-2695-47c2-bc8a-12d064bab6af -TenantID c00b7c2b-ef1b-43f8-8798-2583cb4605db -CertificatePath Cert:\CurrentUser\Computer\3CF88F457CCCE9817ACDB658226031EA0664032B Getting the Token by certificate #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $AppId, [Parameter(Mandatory=$true)] [string] $TenantID, [Parameter(Mandatory=$true)] [string] $CertificatePath ) $Scope = "https://graph.microsoft.com/.default" $Certificate = Get-Item $CertificatePath $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash()) $StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime() $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds $JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0) $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0) $JWTHeader = @{ alg = "RS256" typ = "JWT" x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '=' } $JWTPayLoad = @{ aud = "https://login.microsoftonline.com/$TenantID/oauth2/token" exp = $JWTExpiration iss = $AppId jti = [guid]::NewGuid() nbf = $NotBefore sub = $AppId } $JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json)) $EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte) $JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json)) $EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte) $JWT = $EncodedHeader + "." + $EncodedPayload $PrivateKey = $Certificate.PrivateKey $Signature = Get-SignData -inputObject $PrivateKey -JWT $JWT $JWT = $JWT + "." + $Signature $Body = @{ client_id = $AppId client_assertion = $JWT client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" scope = $Scope grant_type = "client_credentials" } $Url = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" $Header = @{ Authorization = "Bearer $JWT" } $PostSplat = @{ ContentType = 'application/x-www-form-urlencoded' Method = 'POST' Body = $Body Uri = $Url Headers = $Header } Invoke-RestMethod @PostSplat } Export-ModuleMember -Function Get-GraphCertToken #EndRegion - Get-GraphCertToken.ps1 #Region - Get-GraphDeviceAuthToken.ps1 function Get-GraphDeviceAuthToken { <# .SYNOPSIS Function to get a device authentication token. .DESCRIPTION This function works only if your tenant satisfy the pre-requisites below: - Registered Graph API application with required permissions (depends of the requests that you need) - Enabled redirection for Mobile and desktop applications. More details here: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-a-new-application-using-the-azure-portal - Configured redirect URL: https://localhost - '"allowPublicClient": true' in application Manifest json .EXAMPLE PS C:\> Get-GraphDeviceAuthToken -TenantName 'contoso' -AppId '246c7445-eee6-4d60-968d-f83d67183753' Getting the device auth token for Contoso tenant using application ID registered in Azure AD .PARAMETER TenantName You can find your tenant name using Azure AD portal > Overview > Basic information > Name .PARAMETER AppId Фpplication ID registered in Azure AD .INPUTS None. You cannot pipe objects to Get-GraphDeviceAuthToke .OUTPUTS System.Array. Returns the array with token .LINK Source code of this function: https://github.com/aslan-im/GraphApiRequests/blob/main/Functions/Public/Get-GraphDeviceAuthToken.ps1 .LINK Source code of whole project: https://github.com/aslan-im/GraphApiRequests #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $AppId, [Parameter(Mandatory)] [string] $TenantName, [Parameter(Mandatory=$false)] [string] $ApiUrl = "https://graph.microsoft.com/" ) $TenantUrl = "$TenantName.onmicrosoft.com" $AuthUrl = "https://login.microsoftonline.com/$TenantUrl" $CodeRequestSplat = @{ TenantName = $TenantName AppId = $AppId } $DeviceCodeObject = Get-GraphDeviceAuthCode @CodeRequestSplat Write-Output $DeviceCodeObject.message $Code = ($DeviceCodeObject.message -split "code " | Select-Object -Last 1) -split " to authenticate." Set-Clipboard -Value $Code New-GraphAuthFormWindow $TokenParamsSplat = @{ Method = "POST" URI = "$Authurl/oauth2/token" ErrorAction = "Stop" body = @{ grant_type = 'device_code' resource = $ApiUrl client_id = $AppId code = $($DeviceCodeObject.device_code) } } $TokenResponse = $null try { $TokenResponse = Invoke-RestMethod @TokenParamsSplat return $TokenResponse } catch [System.Net.WebException]{ if ($null -eq $_.Exception.Response){ throw } $Result = $_.Exception.Response.GetResponseStream() $Reader = New-Object System.IO.StreamReader($Result) $Reader.BaseStream.Position = 0 $ErrorBody = ConvertFrom-Json $Reader.ReadToEnd() if ($ErrorBody.Error -ne "authorization_pending"){ throw } } } Export-ModuleMember -Function Get-GraphDeviceAuthToken #EndRegion - Get-GraphDeviceAuthToken.ps1 #Region - Get-GraphToken.ps1 Function Get-GraphToken { <# .SYNOPSIS Function for getting token using client secret .DESCRIPTION For using this function you need to have a generated AppSecret (Client secret) in registered application in Azure AD .EXAMPLE PS C:\> Get-GraphToken -AppId '246c7445-eee6-4d60-968d-f83d67183753' -AppSecret '6R[O)5D8sHZ^pt"3' -TenantId 'd1ee13a4-c9d0-4ab0-bff5-c011dfc20717' Example of getting the token .INPUTS None. You cannot pipe objects to Get-GraphDeviceAuthToke .OUTPUTS Returns an array with token .LINK Source code of this function: https://github.com/aslan-im/GraphApiRequests/blob/main/Functions/Public/Get-GraphToken.ps1 .LINK Source code of whole project: https://github.com/aslan-im/GraphApiRequests #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $AppId, [Parameter(Mandatory=$true)] [string] $AppSecret, [Parameter(Mandatory=$true)] [string] $TenantID ) $AuthUrl = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token" $Scope = "https://graph.microsoft.com/.default" $Body = @{ client_id = $AppId client_secret = $AppSecret scope = $Scope grant_type = 'client_credentials' } $PostSplat = @{ ContentType = 'application/x-www-form-urlencoded' Method = 'POST' Body = $Body Uri = $AuthUrl ErrorAction = "Stop" } try { Invoke-RestMethod @PostSplat } catch { throw "Exception was caught: $($_.Exception.Message)" break } } Export-ModuleMember -Function Get-GraphToken #EndRegion - Get-GraphToken.ps1 #Region - Invoke-GraphApiRequest.ps1 function Invoke-GraphApiRequest { <# .SYNOPSIS Function to invoke Graph API Request .DESCRIPTION Using this function you can invoke any request to the Graph API both versions .EXAMPLE PS C:\> $Token = Get-GraphToken -AppId 246c7445-eee6-4d60-968d-f83d67183753 -AppSecret ?2mwmHICkx8j -TenantID d1ee13a4-c9d0-4ab0-bff5-c011dfc20717 PS C:\> Invoke-GraphApiRequest -Token $Token -Resource groups -Method Get Example of using Invoke-GraphApiRequest to get the list of Azure AD Groups. In this example first command is required to get the token using app secret. .EXAMPLE PS C:\> $BodyObject = [PSCustomObject]@{ description = "Self help community for golf" displayName = "Golf Assist" groupTypes = @( "Unified" ) mailEnabled = $true mailNickname = "golfassist" securityenabled = $false } PS C:\> $BodyJson = ConvertTo-Json $BodyObject PS C:\> $Token = Get-GraphToken -AppId 246c7445-eee6-4d60-968d-f83d67183753 -AppSecret ?2mwmHICkx8j -TenantID d1ee13a4-c9d0-4ab0-bff5-c011dfc20717 PS C:\> Invoke-GraphApiRequest -Token $Token -Resource groups -Method POST -Body $BodyJson Steps in this example: 1. Creating a PSCustomObject with payload of MailEnalbed Security Group 2. Converting PSCustomObject to Json 3. Getting the token using Secret (if you already have a token and it is not expired, this step can be skipped) 4. Invoking a request to the Graph API for creating a new group with predefined properties .INPUTS None. You cannot pipe objects to Get-GraphDeviceAuthToke .OUTPUTS Usually it is JSON .LINK Source code of this function: https://github.com/aslan-im/GraphApiRequests/blob/main/Functions/Public/Invoke-GraphApiRequest.ps1 .LINK Source code of whole project: https://github.com/aslan-im/GraphApiRequests #> [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [PSCustomObject] $Token, [Parameter(Mandatory=$True)] [string] $Resource, [Parameter(Mandatory=$false)] [ValidateSet('beta', 'v1.0')] [string] $ApiVersion = 'beta', [Parameter(Mandatory=$false)] [ValidateSet('GET', 'PATCH', 'POST', 'PUT', 'DELETE')] [string] $Method = 'GET', [Parameter(Mandatory=$false)] [string] $TenantId, [Parameter(Mandatory=$false)] [string] $Body, [Parameter(Mandatory=$false)] [string] $AppID, [Parameter(Mandatory=$false)] [string] $AppSecret, [Parameter(Mandatory=$false)] [string] $ApiUrl = 'https://graph.microsoft.com' ) if (!$Token -and $AppId -and $AppSecret -and $TenantId) { try{ $Token = Get-GraphToken -TenantID $TenantId -AppId $AppId -AppSecret $AppSecret -ErrorAction Stop } catch{ throw $_.Exception break } } elseif (!$Token -and !$AppId -and $AppSecret -and $TenantId) { throw "There is no AppId parameter specified. Please run commandlet again with -AppId specified." break } elseif (!$Token -and $AppId -and !$AppSecret) { throw "There is no AppSecret parameter specified. Please run commandlet again with -AppSecret specified." break } elseif (!$Token -and $AppId -and $AppSecret -and !$TenantId) { throw "There is no TenantID parameter specified. Please run commandlet again with -TenantId specified." break } elseif (!$Token -and !$AppId -and !$AppSecret) { throw "Token, AppId, AppSecret or TenantID are not specified. Please run commandlet with Token specified or with AppId and AppSecret." break } if ($Resource[0] -eq '/') { $Resource = $Resource -replace '^.' } $Url = "$ApiUrl/$ApiVersion/$($Resource)" $Header = @{ Authorization = "$($Token.token_type) $($Token.access_token)" } $PostSplat = @{ ContentType = 'application/json' Method = $Method Header = $Header Uri = $Url } if ($Body) { $PostSplat.Add('Body', $Body) } $Result = @() try { $ResultResponse = Invoke-RestMethod @PostSplat -ErrorAction Stop if([bool]($ResultResponse -match "value")){ $Result = $ResultResponse.value } else{ $Result = $ResultResponse } if([bool]($ResultResponse -match "@odata.nextLink")){ $ResultNextLink = $ResultResponse."@odata.nextLink" } if ($ResultNextLink) { while ($null -ne $ResultNextLink){ $PostSplat = @{ ContentType = 'application/json' Method = $Method Header = $Header Uri = $ResultNextLink } $ResultResponse = Invoke-RestMethod @PostSplat -ErrorAction Stop $ResultNextLink = $ResultResponse."@odata.nextLink" $Result += $ResultResponse.value } } return $Result } catch { throw $_.Exception break } } Export-ModuleMember -Function Invoke-GraphApiRequest #EndRegion - Invoke-GraphApiRequest.ps1 #Region - New-GraphCertificate.ps1 function New-GraphCertificate { <# .SYNOPSIS Function for generating new self signed Graph API auth certificate .DESCRIPTION After the creation, certificate can be used for authentication in Graph API .NOTES For running this function you require to provide TenantName (not an ID). .EXAMPLE New-GraphCertificate -TenantName 'Contoso' -StoreLocation 'Cert:\CurrentUser\Computer' Generate new certificate and save in Cert:\CurrentUser\Computer #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [String] $TenantName, [Parameter(Mandatory=$false)] [string] $StoreLocation = 'Cert:\CurrentUser\My', [Parameter(Mandatory=$False)] [string] $CertificateOutputPath = "C:\Temp", [Parameter(Mandatory=$false)] [DateTime][ValidateScript({$_ -ge (Get-Date)})] $ExpirationDate = (Get-Date).AddYears(1), [Parameter(Mandatory=$false)] [string] $FriendlyName = "GraphCert" ) $CreateCertSplat = @{ FriendlyName = $FriendlyName DnsName = $TenantName CertStoreLocation = $StoreLocation NotAfter = $ExpirationDate KeyExportPolicy = "Exportable" KeySpec = "Signature" Provider = "Microsoft Enhanced RSA and AES Cryptographic Provider" HashAlgorithm = "SHA256" ErrorAction = "Stop" } $Certificate = New-SelfSignedCertificate @CreateCertSplat $CertificatePath = Join-Path -Path $StoreLocation -ChildPath $Certificate.Thumbprint If (!(Test-Path -Path $CertificateOutputPath)){ New-Item -ItemType Folder -Path $CertificateOutputPath } $CerOutPath = "$CertificateOutputPath\$FriendlyName.cer" Export-Certificate -Cert $CertificatePath -FilePath $CerOutPath } Export-ModuleMember -Function New-GraphCertificate #EndRegion - New-GraphCertificate.ps1 ### --- PRIVATE FUNCTIONS --- ### #Region - Get-GraphDeviceAuthCode.ps1 function Get-GraphDeviceAuthCode { [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [string] $ApiUrl = "https://graph.microsoft.com/", [Parameter(Mandatory)] [string] $TenantName, [Parameter(Mandatory)] [string] $AppId ) $TenantUrl = "$TenantName.onmicrosoft.com" $AuthUrl = "https://login.microsoftonline.com/$TenantUrl" $PostSPlat = @{ method = 'POST' uri = "$AuthUrl/oauth2/devicecode" ErrorAction = "STOP" body = @{ resource = $ApiUrl client_id = $AppId } } try { Invoke-RestMethod @PostSPlat } catch [System.Net.WebException]{ throw $_.Exception } } #EndRegion - Get-GraphDeviceAuthCode.ps1 #Region - Get-SignData.ps1 function Get-SignData { [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [System.Security.Cryptography.RSACng] $InputObject, [Parameter(Mandatory=$false)] [string] $JWT ) $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1 $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256 [Convert]::ToBase64String( $InputObject.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding) ) -replace '\+','-' -replace '/','_' -replace '=' } #EndRegion - Get-SignData.ps1 #Region - New-GraphAuthFormWindow.ps1 function New-GraphAuthFormWindow { [CmdletBinding()] param ( ) Add-Type -AssemblyName System.Windows.Forms $Form = New-Object -TypeName System.Windows.Forms.Form -Property @{ Width = 440; Height = 640 } $Web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{ Width = 440; Height = 600; Url = "https://www.microsoft.com/devicelogin" } $Web.Add_DocumentCompleted($DocComp) $Web.DocumentText $Form.Controls.Add($Web) $Form.Add_Shown({ $Form.Activate()}) $Web.ScriptErrorsSuppressed = $true $Form.AutoScaleMode = 'Dpi' $Form.Text = "Graph API Authentication" $Form.ShowIcon = $False $Form.AutoSizeMode = 'GrowAndShrink' $Form.StartPosition = 'CenterScreen' $Form.ShowDialog() | Out-Null } #EndRegion - New-GraphAuthFormWindow.ps1 |