Giter Club home page Giter Club logo

psmsgraph's Introduction

Build status Documentation Status

PSMSGraph

This is a PowerShell module API wraper for the Microsoft Graph API.

What is Microsft Graph?

The Microsoft Graph API is a REST API provided by Microsoft for integrating and managing Office 365 Exchange Online, OneDrive for Business, and Azure AD. It allows for application developers to integrate their apps with those Microsoft Services. Management of the environment is also possible but requires understanding of OAuth and REST.

Why use the PSMSGraph module?

This module is an API wrapper. It seeks to take the "foreign" concepts of REST and OAuth and make them accessible and usable in PowerShell. This module strives to make PowerShell administration and automation tasks via the Microsoft Graph API more like other PowerShell commands.

Features

  • In-memory and at-rest security of the Access Token, Refresh Token, and Client Secret. These are all stored in memory as secure strings and are only made plain-text on demand when needed. When exported to disk, they are done so with CLI XML which maintains the secure string.
  • Extensible type (Mark's "Poor Man's Classes") system allow for piping between functions similar to Active Directory or Exchange cmdlets
  • Easy OAuth authorization process with a WinForms authentication popup
  • No "mystery DLL's" required. The entire OAuth authorization, token request, and token refresh process is written in pure PowerShell
  • Export and Import access tokens between sessions allowing you to authorize an application once and reuse the token until the refresh expires from lack of use or is revoked. Great for automation!
  • No hassle Token Refreshing!! Calls to Invoke-GraphRequest (and all the functions that utalize it) automatically track the renewal needs for your Access Tokens and will automatically refresh them when needed.

Installation

PSMSGraph is available on the PowerShell Gallery.

To Inspect:

Save-Module -Name PSMSgraph -Path <path> 

To install:

Install-Module -Name PSMSgraph 

Documentaion

Documentation Site: psmsgraph.readthedocs.io

Quickstart

Create an Azure AD Application

  1. Go to https://apps.dev.microsoft.com/
  2. Register an app using your Office 365 or Azure AD account (the account must have permissions to add applications to you Azure AD)
  3. Generate a new password for your app
  4. Give the app the proper scope permissions
  5. Set an arbitrary Redirect URI (e.g. https://localhost/)
  6. Note your Redirect URI, Application ID, and the password that was generated. The Application ID is your "Client ID" and the password is your "Client Secret". These are not your O365/Azure username and password.

Authorize the app and export your Access Token

Import-Module -name 'PSMSGraph'
#In the credential prompt, provide your application's Client ID as the username and Client Secret as the password
$ClientCredential = Get-Credential
$GraphAppParams = @{
    Name = 'My Graph Application!'
    ClientCredential = $ClientCredential
    RedirectUri = 'https://localhost/'
    Tenant = 'adatum.onmicrosoft.com'

}
$GraphApp = New-GraphApplication @GraphAppParams
# This will prompt you to log in with your O365/Azure credentials. 
# This is required at least once to authorize the application to act on behalf of your account
# The username and password is not passed back to or stored by PowerShell.
$AuthCode = $GraphApp | Get-GraphOauthAuthorizationCode 
# see the following help for what resource to use. 
# get-help Get-GraphOauthAccessToken -Parameter Resource
$GraphAccessToken = $AuthCode | Get-GraphOauthAccessToken -Resource 'https://graph.windows.net'
$GraphAccessToken | Export-GraphOAuthAccessToken -Path 'c:\MyGraphApp\AccessToken.XML'

Build a script to pull in all Azure AD users

Import-Module -name 'PSMSGraph'
$GraphAccessToken =  Import-GraphOAuthAccessToken -Path 'c:\MyGraphApp\AccessToken.XML'
$GraphAccessToken | Update-GraphOAuthAccessToken -Force

$AADUsers = Get-AADUserAll -AccessToken $GraphAccessToken
$AADUsers | 
    Select-Object -Property * -ExcludeProperty _AccessToken | 
    Export-Csv -Path 'c:\MyGraphApp\AADUsers.csv' -NoTypeInformation

$GraphAccessToken  | Export-GraphOAuthAccessToken -Path 'c:\MyGraphApp\AccessToken.XML'

Release Notes

https://github.com/markekraus/PSMSGraph/blob/master/RELEASE.md

ChangeLog

https://github.com/markekraus/PSMSGraph/blob/master/docs/ChangeLog.md

psmsgraph's People

Contributors

gavineke avatar kevinmarquette avatar m1kep avatar markdomansky avatar markekraus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

psmsgraph's Issues

Support for Powershell 6 (.net Core) missing

Importing the module into a powershell 6.1 session throws an error due to dependency on System.Windows.Forms. Are there any plans or work underway to support pwsh.exe as a host?

thanks for your great work!

BG Christoph

Import-Module : Could not load file or assembly 'System.Windows.Forms, Culture=neutral, PublicKeyToken=null'. Das System kann die angegebene Datei nicht finden.
At line:1 char:1
+ Import-Module psmsgraph
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidOperation: (:) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : FormatXmlUpdateException,Microsoft.PowerShell.Commands.ImportModuleCommand

The expires property is not set correctly

Currently, the field 'expires' on my OathAccesstoken object is a few seconds before the current time, while it is actually valid for an hour. This means I cannot use the Expires property to check if I need to update the access token or not.

Looking into the code, in the type definition, I notice the following code
if ($This.Response.expires_on) { (get-date '1970/01/01 -0').AddSeconds($This.Response.expires_on) }
On my local machine (get-date '1970/01/01 -0') translates to Thursday, January 1, 1970 1:00:00 AM. (I'm currently on UTC + 2).

I assume expires_on uses UTC time.

In that case I expect the solution to be like this (if you want your users to use UTC):

if ($This.Response.expires_on) { (get-date '1970/01/01 -0').ToUniversalTime().AddSeconds($This.Response.expires_on) }

I didn't check if this issue also exists for the other datetime fields

100% Code Coverage for Add-AADAppRoleAssignment

Code coverage report:
Covered 0 % of 33 analyzed Commands in Add-AADAppRoleAssignment
Missed commands:

Line Command
78 $ServicePrincipal
79 $User
80 $ProcessText = "User: '{0}' ServicePrincipal: '{1}' RoleID: '{2}'" -f $UserObject.ObjectId, $ServiceObject.ObjectId, $RoleID
81 if (-not $pscmdlet.ShouldProcess($ProcessText)) { ...
84 $AccessToken = $ServiceObject._AccessToken
85 $Application = $AccessToken.Application
86 $Tenant = $Application.Tenant
87 $Body = @{ ...
88 id = $RoleID
89 resourceId = $ServiceObject.ObjectId
90 principalId = $UserObject.ObjectId
91 $Body = @{ ...
92 $Url = '{0}/{1}/{2}/{3}/{4}?api-version={5}' -f @( ...
93 $BaseUrl
94 $Tenant
95 'users'
96 $UserObject.ObjectId
97 'appRoleAssignments'
98 $APIversion
100 $Params = @{ ...
101 Uri = $Url
102 Body = $Body
103 Method = 'POST'
104 AccessToken = $AccessToken
105 ErrorAction = 'Stop'
108 $Result = Invoke-GraphRequest @Params
111 ``` $ErrorMessage = "Unable to add App Assignments for User '{0}' to ServicePrincipal '{1}': {2}" -f $UserObject.ObjectId, $ServiceObject.Objec
tId, $_.Exception.Message ```
112 Write-Error $ErrorMessage
115 $OutputObject = $Result.ContentObject.psobject.copy()
116 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.AppRoleAssignment')
117 $OutputObject
117 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
118 $OutputObject

Token Expired when Obtained

Similar to #31, I am running into the this problem suddenly

PSMSGraph Version: 1.0.26.43

Relevant code:
https://github.com/ReArmedHalo/DUST/blob/develop/DUST/public/Microsoft%20365%20Health%20Check%20Reports/Invoke-Microsoft365HealthCheck.ps1

Building the application in Azure AD: https://github.com/ReArmedHalo/DUST/blob/develop/DUST/private/Microsoft%20365%20Health%20Check/Support/New-DUSTAzureADApiApplication.ps1

Using PSMSGraph to get consent for permissions and an access token: https://github.com/ReArmedHalo/DUST/blob/develop/DUST/private/Microsoft%20365%20Health%20Check/Support/Get-DUSTAzureADApiApplicationConsent.ps1

VERBOSE: Retrieving OAuth Access Token from https://login.microsoftonline.com/common/oauth2/token... VERBOSE: POST https://login.microsoftonline.com/common/oauth2/token with -1-byte payload VERBOSE: received 4142-byte response of content type application/json; charset=utf-8 VERBOSE: Access Token: Guid: <blah> IsExpired 'True'

Everything was working fine about two weeks ago. I then came back in and tried to test a new report I was trying to grab and determined I could no longer get a valid token. I don't remember what that error was but I went through a few iteration of trying other code and ended up back here after a week of troubleshooting.

100% Code Coverage for Get-AADServicePrincipalAppRoleAssignedTo

Code coverage report:
Covered 0 % of 29 analyzed Commands in Get-AADServicePrincipalAppRoleAssignedTo
Missed commands:

Line Command
61 $ServicePrincipal
62 if (-not $pscmdlet.ShouldProcess($ServiceObject.objectId)) { ...
65 $AccessToken = $ServiceObject._AccessToken
66 $Application = $AccessToken.Application
67 $Tenant = $Application.Tenant
68 $SkipToken = $null
70 $Url = '{0}/{1}/{2}/{3}/{4}?api-version={5}{6}' -f @( ...
71 $BaseUrl
72 $Tenant
73 'servicePrincipals'
74 $ServiceObject.objectId
75 'appRoleAssignedTo'
76 $APIversion
77 $SkipToken
79 $Params = @{ ...
80 Uri = $Url
81 Method = 'GET'
82 AccessToken = $AccessToken
83 ErrorAction = 'Stop'
86 $Results = Invoke-GraphRequest @Params
89 $ErrorMessage = "Unable to query App Assignments for service principal '{0}': {1}" -f $ServiceObject.objectId, $_.Exception.Message
90 Write-Error $ErrorMessage
93 $Results.ContentObject.value
94 $OutputObject = $Result.psobject.copy()
95 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.AppRoleAssignment')
96 $OutputObject
96 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
97 $OutputObject
99 $SkipToken = $Results.ContentObject.'odata.nextLink' -replace '^.*skiptoken', '&$skiptoken'

100% Code Coverage for Remove-AADAppRoleAssignment

Code coverage report:
Covered 0 % of 21 analyzed Commands in Remove-AADAppRoleAssignment
Missed commands:

Line Command
70 $AppRoleAssignment
71 if (-not $pscmdlet.ShouldProcess($AppRole.ObjectId)) { ...
74 $AccessToken = $AppRole._AccessToken
75 $Application = $AccessToken.Application
76 $Tenant = $Application.Tenant
77 $Url = '{0}/{1}/{2}/{3}/{4}/{5}?api-version={6}' -f @( ...
78 $BaseUrl
79 $Tenant
80 'users'
81 $AppRole.principalId
82 'appRoleAssignments'
83 [System.Web.HttpUtility]::UrlEncode($AppRole.ObjectId)
84 $APIversion
86 $Params = @{ ...
87 Uri = $Url
88 Method = 'DELETE'
89 AccessToken = $AccessToken
90 ErrorAction = 'Stop'
93 Invoke-GraphRequest @Params
96 $ErrorMessage = "Unable to remove App Assignments for App Role Assignment '{0}': {1}" -f $AppRole.ObjectId, $_.Exception.Message
97 Write-Error $ErrorMessage

100% Code Coverage for Export-GraphOauthAccessToken

Code coverage report:
Covered 0 % of 19 analyzed Commands in Export-GraphOauthAccessToken
Missed commands:

Line Command
108 $ExportProperties = $AccessToken.psobject.Properties.where({ $_.MemberType -ne 'ScriptProperty' }).Name
108 $_.MemberType -ne 'ScriptProperty'
109 Write-Verbose "Propertes: $($ExportProperties -join ' ')"
109 $ExportProperties -join ' '
110 $ExportToken = $AccessToken ...
110 $ExportToken = $AccessToken ...
111 $PsCmdlet.ParameterSetName
113 $Params = @{ ...
114 Encoding = $Encoding
115 Path = $Path
116 InputObject = $ExportToken
118 $Target = $Path
121 $Params = @{ ...
122 Encoding = $Encoding
123 LiteralPath = $LiterlPath
124 InputObject = $ExportToken
126 $Target = $LiteralPath
129 if ($pscmdlet.ShouldProcess($Target)) { ...
130 Export-Clixml @Params

100% Code Coverage for Get-AADGroupMember

Code coverage report:
Covered 93.33 % of 30 analyzed Commands in Get-AADGroupMember
Missed commands:

Line Command
114 $ErrorMessage = "Unable to query members for group '{0}': {1}" -f $GroupObject.objectId, $_.Exception.Message
115 Write-Error $ErrorMessage

100% Code Coverage for Update-GraphOauthAccessToken

Code coverage report:
Covered 0 % of 38 analyzed Commands in Update-GraphOauthAccessToken
Missed commands:

Line Command
72 [system.uri]::IsWellFormedUriString( ...
86 $AccessToken
87 Write-Verbose "Processing token '$($RefreshToken.GUID.ToString())'"
87 $RefreshToken.GUID.ToString()
88 If (!$AccessToken.isExpired -and !$Force -and (get-date) -lt $AccessToken.Expires.addseconds(-$RenewalPeriod)) { ...
88 get-date
89 Write-Verbose "Token is not expired. Skipping"
92 $Body = @( ...
93 'grant_type=refresh_token'
94 '&redirect_uri={0}' -f [System.Web.HttpUtility]::UrlEncode($RefreshToken.Application.RedirectUri)
95 '&client_id={0}' -f [System.Web.HttpUtility]::UrlEncode($RefreshToken.Application.ClientID)
96 '&client_secret={0}' -f [System.Web.HttpUtility]::UrlEncode($RefreshToken.Application.GetClientSecret())
97 '&refresh_token={0}' -f [System.Web.HttpUtility]::UrlEncode($RefreshToken.GetRefreshToken())
98 '&resource={0}' -f [System.Web.HttpUtility]::UrlEncode($RefreshToken.Resource)
100 $Params = @{ ...
101 Uri = $BaseUrl
102 WebSession = $RefreshToken.Session
103 Method = 'POST'
104 Body = $Body
106 $RequestTime = Get-Date
108 $WebRequest = Invoke-WebRequest @Params
111 $ErrorMessage = $_.Exception.Message
112 Write-Error "Failed to refresh token: $ErrorMessage"
116 $Content = $WebRequest.Content ...
116 $Content = $WebRequest.Content ...
119 $ErrorMessage = $_.Exception.Message
120 $Message = "Failed to convert response from JSON: {0}" -f $ErrorMessage
121 Write-Error $Message
122 Write-Error $WebRequest.Content
125 $RefreshToken.AccessTokenCredential = [pscredential]::new('access_token', $($Content.access_token ...
125 $Content.access_token
125 ConvertTo-SecureString -AsPlainText -Force
126 $RefreshToken.Response = $Content ...
126 $RefreshToken.Response = $Content ...
127 $RefreshToken.RequestedDate = $RequestTime
129 if ($PassThru) { ...
130 Write-Verbose "Sending Token to the Pipeline"
131 $RefreshToken

100% Code Coverage for Get-AADServicePrinicpalbyDisplayName

Code coverage report:
Covered 0 % of 24 analyzed Commands in Get-AADServicePrinicpalbyDisplayName
Missed commands:

Line Command
76 $DisplayName
77 if (-not $pscmdlet.ShouldProcess($ServiceId)) { ...
80 $Application = $AccessToken.Application
81 $Tenant = $Application.Tenant
82 $Url = '{0}/{1}/{2}?api-version={3}&$filter=displayName+eq+%27{4}%27' -f @( ...
83 $BaseUrl
84 $Tenant
85 'servicePrincipals'
86 $APIversion
87 [System.Web.HttpUtility]::UrlEncode($ServiceName)
89 $Params = @{ ...
90 Uri = $Url
91 Method = 'GET'
92 AccessToken = $AccessToken
93 ErrorAction = 'Stop'
96 $Result = Invoke-GraphRequest @Params
99 $ErrorMessage = "Unable to query User '{0}': {1}" -f $UserId, $_.Exception.Message
100 Write-Error $ErrorMessage
103 $Result.ContentObject.value
104 $OutputObject = $ServiceObject.psobject.copy()
105 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.ServicePrincipal')
106 $OutputObject
106 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
107 $OutputObject

BaseURL in Get-GraphOauthAuthorizationCode should default to \authorize?

I think the default of BaseUrl should default to end with "?" so that it is possible to provide a baseurl with custom added parameters. For instance i want to utilize the optional parameter "prompt=select_account" to force the user to get a prompt asking the user to select an account. At the moment the "?" is added on the line 69;

$Url = "{0}?response_type=code&redirect_uri={1}&client_id={2}" -f @(

So if I would provide the custom parameter in the baseurl it would be:
$BaseURL = ....\authorize?prompt=select_account&
which would result in the following concatinated string:
\authorize?prompt=select_account&?response_type=code&....

If the "?" is appended to the default baseurl and removed from the string-formater it would be possible to override the first parameter.

So if I then provide the same BaseUrl the resulting URL would instead be correct;
$BaseURL = ....\authorize?prompt=select_account&
\authorize?prompt=select_account&response_type=code&....

Thanks!

100% Code Coverage for Get-GraphOauthAccessToken

Code coverage report:
Covered 66.67 % of 60 analyzed Commands in Get-GraphOauthAccessToken
Missed commands:

Line Command
134 $response = $_.Exception.Response
135 $Stream = $response.GetResponseStream()
136 $Stream.Position = 0
137 $StreamReader = New-Object System.IO.StreamReader $Stream
138 $ResponseBody = $StreamReader.ReadToEnd()
139 $ErrorMessage = "Requesting OAuth Access Token from '{0}' Failed: {1}: {2}" -f $BaseURL, ...
141 Write-Error -message $ErrorMessage -Exception $_.Exception
148 $ErrorMessage = $_.Exception.Message
149 $Params = @{ ...
150 MemberType = 'NoteProperty'
151 Name = 'Respone'
152 Value = $Result
154 $_.Exception
154 Add-Member @Params
155 $Message = "Failed to convert response from JSON: {0}" -f $ErrorMessage
156 Write-Error -Exception $_.Exception -Message $Message
159 $Content.access_token
159 ConvertTo-SecureString -AsPlainText -Force
160 $Content.refresh_token
160 ConvertTo-SecureString -AsPlainText -Force

100% Code Coverage for Get-AADUserByID

Code coverage report:
Covered 0 % of 23 analyzed Commands in Get-AADUserByID
Missed commands:

Line Command
74 $ObjectId
75 if (-not $pscmdlet.ShouldProcess($UserId)) { ...
78 $Application = $AccessToken.Application
79 $Tenant = $Application.Tenant
80 $Url = '{0}/{1}/{2}/{3}?api-version={4}' -f @( ...
81 $BaseUrl
82 $Tenant
83 'users'
84 $UserId
85 $APIversion
87 $Params = @{ ...
88 Uri = $Url
89 Method = 'GET'
90 AccessToken = $AccessToken
91 ErrorAction = 'Stop'
94 $Result = Invoke-GraphRequest @Params
97 $ErrorMessage = "Unable to query User '{0}': {1}" -f $UserId, $_.Exception.Message
98 Write-Error $ErrorMessage
101 $OutputObject = $Result.ContentObject.psobject.copy()
102 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.User')
103 $OutputObject
103 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
104 $OutputObject

Get-GraphOauthAuthorizationCode -ForcePrompt none causes AADSTS50059

I am attempting to use the daemon methodology for making WebAPI calls to Azure Graph.

However, when setting -ForcePrompt to none, I get AADSTS50059 error: No tenant-identifying information found in either the request or implied by any provided credentials.

I believe this may have something to do with using the "common" URI and not supplying the tenant information.

I believe if a $tenantId variable is added to the objects, it will solve this issue. Other code will probably be needed around the IF looking for the ForcePrompt, ie: If ForcePrompt = none, insert tenantId in to BaseUrl.

I am not familiar with github and pull requests, otherwise, I would implement this and push the code here for a merge.

Please let me know if this is on the right track? I'm going to test it now.

Thank you for your hard work on this!!!

Invoke-graphrequest refreshes the token

I noticed the other day that Invoke-graphrequest always calls Update-GraphOauthAccessToken. This surprises me, because if you call Invoke-graphrequest more than once per hour you're inefficient. The type of code I'd want to use (and actually implemented) is something like this:
While($true){ $GraphAccessToken = Import-GraphOAuthAccessToken -Path $PSScriptRoot'\AccessToken.XML' if($GraphAccessToken.IsExpired) { $GraphAccessToken | Update-GraphOAuthAccessToken } Invoke-GraphRequest }
However, the if clause is useless and in fact reduces the performance, because it performs an action that is already performed later on anyway. At the very least I'd expect this same if clause before the token is refreshed....
Is there a reason why you refresh the token before every call?

100% Code Coverage for Get-AADGroupByDisplayName

Code coverage report:
Covered 91.67 % of 24 analyzed Commands in Get-AADGroupByDisplayName
Missed commands:

Line Command
104 $ErrorMessage = "Unable to query User '{0}': {1}" -f $UserId, $_.Exception.Message
105 Write-Error $ErrorMessage

100% Code Coverage for Invoke-GraphRequest

Code coverage report:
Covered 0 % of 66 analyzed Commands in Invoke-GraphRequest
Missed commands:

Line Command
134 Write-Verbose "Performing token refresh"
135 $AccessToken
135 Update-GraphOauthAccessToken -ErrorAction Stop
138 $ErrorMessage = "Unable to refresh Access Token '{0}': {1}" -f $AccessToken.GUID, $_.Exception.Message
139 Write-Error $ErrorMessage
142 if (-not $pscmdlet.ShouldProcess("$Uri")) { ...
145 Write-Verbose "Set base parameters"
146 $Params = @{ ...
147 ContentType = $ContentType
148 Uri = $Uri
149 WebSession = $AccessToken.Session
150 Method = $Method
151 ErrorAction = 'Stop'
153 if ($Body) { ...
154 Write-Verbose "Setting Body Parameter"
155 $Params['Body'] = $Body
157 if ($TimeoutSec) { ...
158 Write-Verbose "Setting TimeoutSec Parameter"
159 $Params['TimeoutSec'] = $TimeoutSec
161 Write-Verbose "Setting Headers Parameter"
162 $Params['Headers'] = @{ }
163 if ($Headers) { ...
164 Write-Verbose "Setting user supplied headers"
165 $Params['Headers'] = $Headers
167 Write-Verbose "Setting Authorization header"
168 $Params['Headers']['Authorization'] = 'Bearer {0}' -f $AccessToken.GetAccessToken()
169 $RequestedDate = Get-Date
171 $Result = Invoke-WebRequest @Params
172 $ReceivedDate = Get-Date
175 $response = $_.Exception.Response
176 $Stream = $response.GetResponseStream()
177 $Stream.Position = 0
178 $StreamReader = New-Object System.IO.StreamReader $Stream
179 $ResponseBody = $StreamReader.ReadToEnd()
180 $ErrorMessage = "Unable to query Uri '{0}': {1}: {2}" -f $Uri, $_.Exception.Message, $ResponseBody
181 Set-Variable -Scope global -Name _invokeGraphRequestException -Value $_
182 Write-Error -message $ErrorMessage -Exception $_.Exception
185 Write-Verbose "Truncating Authorization header"
187 $Params['Headers']['Authorization'] = '{0}...{1}<truncated>' -f $Params.Headers.Authorization.Substring(0, 25), ...
188 $Params.Headers.Authorization.Length - 11
191 Write-Verbose "No Authorization header to truncate"
193 $Result.Headers.'Content-Type'
194 $_ -match 'application/json'
195 Write-Verbose "Converting result from JSON to PSObject"
196 $ConentObject = $Result.Content ...
196 $ConentObject = $Result.Content ...
199 $_ -match 'application/xml'
200 Write-Verbose "Converting result from XML to PSObject"
201 [xml]$ConentObject = $Result.Content
205 Write-Verbose "Unhandled Content-Type. ContentObject will be raw."
206 $ConentObject = $Result.Content
209 Write-Verbose "Setting LastRequestDate on Access Token"
210 $AccessToken.LastRequestDate = $RequestedDate
211 [pscustomobject]@{ ...
212 PSTypeName = 'MSGraphAPI.RequestResult'
213 Result = $Result
214 Uri = $Uri
215 Headers = $Params.Headers
216 InvokeWebRequestParameters = $Params
217 ContentType = $ContentType
218 TimeoutSec = $TimeoutSec
219 Body = $Body
220 RequestedDate = $RequestedDate
221 RecievedDate = $ReceivedDate
222 AccessToken = $AccessToken
223 ContentObject = $ConentObject

100% Code Coverage for Get-AADUserByUserPrincipalName

Code coverage report:
Covered 0 % of 23 analyzed Commands in Get-AADUserByUserPrincipalName
Missed commands:

Line Command
76 $UserPrincipalName
77 if (-not $pscmdlet.ShouldProcess($UserId)) { ...
80 $Application = $AccessToken.Application
81 $Tenant = $Application.Tenant
82 $Url = '{0}/{1}/{2}/{3}?api-version={4}' -f @( ...
83 $BaseUrl
84 $Tenant
85 'users'
86 $UPN
87 $APIversion
89 $Params = @{ ...
90 Uri = $Url
91 Method = 'GET'
92 AccessToken = $AccessToken
93 ErrorAction = 'Stop'
96 $Result = Invoke-GraphRequest @Params
99 $ErrorMessage = "Unable to query User '{0}': {1}" -f $UPN, $_.Exception.Message
100 Write-Error $ErrorMessage
103 $OutputObject = $Result.ContentObject.psobject.copy()
104 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.User')
105 $OutputObject
105 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
106 $OutputObject

100% Code Coverage for Get-GraphOauthAuthorizationCode

Code coverage report:
Covered 0 % of 53 analyzed Commands in Get-GraphOauthAuthorizationCode
Missed commands:

Line Command
64 if (-not $pscmdlet.ShouldProcess($Application.ClientID)) { ...
67 $Client_Id = [System.Web.HttpUtility]::UrlEncode($Application.ClientId)
68 $Redirect_Uri = [System.Web.HttpUtility]::UrlEncode($Application.RedirectUri)
69 $Url = "{0}?response_type=code&redirect_uri={1}&client_id={2}" -f @( ...
70 $BaseURL
71 $Redirect_Uri
72 $Client_Id
74 Write-Verbose "URL: '$URL'"
75 $Params = @{ ...
76 TypeName = 'System.Windows.Forms.Form'
77 Property = @{ ...
78 Width = 440
79 Height = 640
82 $Form = New-Object @Params
83 $Params = @{ ...
84 TypeName = 'System.Windows.Forms.WebBrowser'
85 Property = @{ ...
86 Width = 420
87 Height = 600
88 Url = $Url
91 $Web = New-Object @Params
92 $DocumentCompleted_Script = { ...
93 if ($web.Url.AbsoluteUri -match "error=[^&]*...
94 $form.Close()
98 $web.ScriptErrorsSuppressed = $false
99 $web.Add_DocumentCompleted($DocumentCompleted_Script)
100 $form.Controls.Add($web)
101 $form.Add_Shown({ $form.Activate() })
101 $form.Activate()
102 [void]$form.ShowDialog()
104 $QueryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query)
105 $Response = @{ }
106 $queryOutput.Keys
107 $Response["$key"] = $QueryOutput[$key]
109 $SecAuthCode = 'NOAUTHCODE' ...
109 $SecAuthCode = 'NOAUTHCODE' ...
110 $AuthCodeCredential = [pscredential]::new('NOAUTHCODE', $SecAuthCode)
111 if ($Response.Code) { ...
112 $SecAuthCode = $Response.Code ...
112 $SecAuthCode = $Response.Code ...
113 $AuthCodeCredential = [pscredential]::new('AuthCode', $SecAuthCode)
114 $Response.Remove('Code')
116 [pscustomobject]@{ ...
117 PSTypeName = 'MSGraphAPI.Oauth.AuthorizationCode'
118 AuthCodeCredential = $AuthCodeCredential
119 ResultURL = $web.Url.psobject.copy()
120 Application = $Application
121 AuthCodeBaseURL = $BaseURL
122 Response = $Response
123 Issued = Get-date
125 [void]$form.Close()
126 [void]$Web.Dispose()
127 [void]$Form.Dispose()

100% Code Coverage for Get-AADUserAppRoleAssignment

Code coverage report:
Covered 0 % of 29 analyzed Commands in Get-AADUserAppRoleAssignment
Missed commands:

Line Command
62 $User
63 if (-not $pscmdlet.ShouldProcess($UserObject.objectId)) { ...
66 $AccessToken = $UserObject._AccessToken
67 $Application = $AccessToken.Application
68 $Tenant = $Application.Tenant
69 $SkipToken = $null
71 $Url = '{0}/{1}/{2}/{3}/{4}?api-version={5}{6}' -f @( ...
72 $BaseUrl
73 $Tenant
74 'users'
75 $UserObject.objectId
76 'appRoleAssignments'
77 $APIversion
78 $SkipToken
80 $Params = @{ ...
81 Uri = $Url
82 Method = 'GET'
83 AccessToken = $AccessToken
84 ErrorAction = 'Stop'
87 $Results = Invoke-GraphRequest @Params
90 $ErrorMessage = "Unable to query App Assignments for service principal '{0}': {1}" -f $UserObject.objectId, $_.Exception.Message
91 Write-Error $ErrorMessage
94 $Results.ContentObject.value
95 $OutputObject = $Result.psobject.copy()
96 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.AppRoleAssignment')
97 $OutputObject
97 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
98 $OutputObject
100 $SkipToken = $Results.ContentObject.'odata.nextLink' -replace '^.*skiptoken', '&$skiptoken'

Scopes not inherited from App

Hi!

Not sure if this is in fact a bug or whatnot, but the scopes that I have set on the Azure App portal are not being granted to the GraphAccessToken.

The only permissions that are received are:
{Mail.ReadWrite, Mail.Send, offline_access, User.Read}

Is this the expected behaviour? Is there some way to control this?

Thanks!

100% Code Coverage for Export-GraphApplication

Code coverage report:
Covered 0 % of 19 analyzed Commands in Export-GraphApplication
Missed commands:

Line Command
106 $ExportProperties = $Application.psobject.Properties.where({ $_.MemberType -ne 'ScriptProperty' }).Name
106 $_.MemberType -ne 'ScriptProperty'
107 Write-Verbose "Propertes: $($ExportProperties -join ' ')"
107 $ExportProperties -join ' '
108 $ExportApplication = $Application ...
108 $ExportApplication = $Application ...
109 $PsCmdlet.ParameterSetName
111 $Params = @{ ...
112 Encoding = $Encoding
113 Path = $Path
114 InputObject = $ExportApplication
116 $Target = $Path
119 $Params = @{ ...
120 Encoding = $Encoding
121 LiteralPath = $LiterlPath
122 InputObject = $ExportApplication
124 $Target = $LiteralPath
127 if ($pscmdlet.ShouldProcess("Target")) { ...
128 Export-Clixml @Params

100% Code Coverage for Import-GraphApplication

Code coverage report:
Covered 64.71 % of 17 analyzed Commands in Import-GraphApplication
Missed commands:

Line Command
75 $ImportFiles = $LiteralPath
76 $ImportParam = 'LiteralPath'
89 $ErrorMessage = "Unable to import from '{0}': {1}" -f @( ...
90 $ImportFile
91 $_.Exception.Message
93 Write-Error $ErrorMessage

Getting (400) Bad Request

Get-GraphOauthAccessToken : The remote server returned an error: (400) Bad Request.
At line:19 char:33
+ ... $AuthCode | Get-GraphOauthAccessToken -Resource 'https://graph.micros ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WebException
    + FullyQualifiedErrorId : System.Net.WebException,Get-GraphOauthAccessToken

Here is my code:

Import-Module -name 'PSMSGraph'
#In the credential prompt, provide your application's Client ID as the username and Client Secret as the password
$ClientCredential = Get-Credential
$GraphAppParams = @{
    Name = 'PowerShell Module'
    ClientCredential = $ClientCredential
    RedirectUri = 'https://localhost/'
    Tenant = 'mytenant.onmicrosoft.com'

}
$GraphApp = New-GraphApplication @GraphAppParams
# This will prompt you to log in with your O365/Azure credentials. 
# This is required at least once to authorize the application to act on behalf of your account
# The username and password is not passed back to or stored by PowerShell.
$AuthCode = $GraphApp | Get-GraphOauthAuthorizationCode
Write-Host $AuthCode
# see the following help for what resource to use. 
# get-help Get-GraphOauthAccessToken -Parameter Resource
$GraphAccessToken = $AuthCode | Get-GraphOauthAccessToken -Resource 'https://graph.microsoft.com'
$GraphAccessToken | Export-GraphOAuthAccessToken -Path 'c:\Temp\AccessToken.XML'

I've setup my app using the documentation found here:

http://thelazyadministrator.com/2018/03/20/connect-to-the-microsoft-graph-api-with-powershell/

Unsure why it's failing with bad request.

100% Code Coverage for Get-AADUserAll

Code coverage report:
Covered 0 % of 26 analyzed Commands in Get-AADUserAll
Missed commands:

Line Command
69 if (-not $pscmdlet.ShouldProcess($AccessToken.GUID)) { ...
72 $Application = $AccessToken.Application
73 $Tenant = $Application.Tenant
74 $SkipToken = $null
76 $Url = '{0}/{1}/{2}?api-version={3}{4}{5}' -f @( ...
77 $BaseUrl
78 $Tenant
79 'users'
80 $APIversion
81 '&$filter={0}' -f [System.Web.HttpUtility]::UrlEncode($Filter)
82 $SkipToken
84 $Params = @{ ...
85 Uri = $Url
86 Method = 'GET'
87 AccessToken = $AccessToken
88 ErrorAction = 'Stop'
91 $Results = Invoke-GraphRequest @Params
94 $ErrorMessage = "Unable to query members for user: {0}" -f $_.Exception.Message
95 Write-Error $ErrorMessage
98 $Results.ContentObject.value
99 $OutputObject = $Result.psobject.copy()
100 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.User')
101 $OutputObject
101 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
102 $OutputObject
104 $SkipToken = $Results.ContentObject.'odata.nextLink' -replace '^.*skiptoken', '&$skiptoken'

100% Code Coverage for Get-AADGroupByID

Code coverage report:
Covered 91.3 % of 23 analyzed Commands in Get-AADGroupByID
Missed commands:

Line Command
103 $ErrorMessage = "Unable to query Group '{0}': {1}" -f $GroupId, $_.Exception.Message
104 Write-Error $ErrorMessage

100% Code Coverage for Import-GraphOauthAccessToken

Code coverage report:
Covered 83.72 % of 43 analyzed Commands in Import-GraphOauthAccessToken
Missed commands:

Line Command
73 $ImportFiles = $LiteralPath
87 $ErrorMessage = "Unable to import from '{0}': {1}" -f @( ...
88 $ImportFile
89 $_.Exception.Message
91 Write-Error $ErrorMessage
101 $Session.Headers[$_.key] = $_.value
105 Write-Warning "Session headers could not be imported."

Updating an imported access token doesn't work in PowerShell 7

While writing this I kept digging deeper to find a solution, and I think I've got it, so I'd like to explain my thought process :)

I upgraded to PowerShell 7 today, and Update-GraphOauthAccessToken broke. After using Export-GraphOauthAccessToken and Import-GraphOauthAccessToken to export & import an access token, if you then try to update the token with Update-GraphOauthAccessToken, it fails with the error:

Update-GraphOauthAccessToken: Failed to refresh token: The format of value '' is invalid.
In PowerShell 5.1, this works fine.

I tracked the error down to the WebSession header in the Invoke-Webrequest call (line 102 of Update-GraphOauthAccessToken.ps1). Omitting that header fixed the issue. But since the header works fine in PowerShell 5.1, I looked into it a bit further.

I compared the Session property of an original access token and an imported token, and it seems the Session property loses the content of the UserAgent subproperty on serializing/deserializing.

Manually adding the user agent back fixed the issue. Next, I checked the XML that's saved to disk, and the user agent is present there, so the issue has to be with Import-GraphOauthAccessToken.ps1. I think I located a bug at line 110, which reads

$Session.UserAgent = $Application.UserAgent

If I'm not mistaken, this should be

$Session.UserAgent = $InObject.Session.UserAgent

This will properly deserialize the UserAgent property and keep Invoke-WebRequest happy :)

100% Code Coverage for Get-AADServicePrinicpalbyId

Code coverage report:
Covered 0 % of 23 analyzed Commands in Get-AADServicePrinicpalbyId
Missed commands:

Line Command
75 $ObjectId
76 if (-not $pscmdlet.ShouldProcess($ServiceId)) { ...
79 $Application = $AccessToken.Application
80 $Tenant = $Application.Tenant
81 $Url = '{0}/{1}/{2}/{3}?api-version={4}' -f @( ...
82 $BaseUrl
83 $Tenant
84 'servicePrincipals'
85 $ServiceId
86 $APIversion
88 $Params = @{ ...
89 Uri = $Url
90 Method = 'GET'
91 AccessToken = $AccessToken
92 ErrorAction = 'Stop'
95 $Result = Invoke-GraphRequest @Params
98 $ErrorMessage = "Unable to query User '{0}': {1}" -f $UserId, $_.Exception.Message
99 Write-Error $ErrorMessage
102 $OutputObject = $Result.ContentObject.psobject.copy()
103 $OutputObject.psobject.TypeNames.Insert(0, 'MSGraphAPI.DirectoryObject.ServicePrincipal')
104 $OutputObject
104 Add-Member -MemberType NoteProperty -Name _AccessToken -Value $AccessToken
105 $OutputObject

OAuth Access Token is expired at creation

I've re-created the app and tried all these steps as a regular user and admin in our tenant and on two different Win 10 workstations.

https://apps.dev.microsoft.com
Redirect URLs: 'https://localhost/'
Delegated Permissions: 'Mail.ReadWrite' and 'User.Read'
Application Permissions: 'Mail.ReadWrite (Admin Only)' and 'User.Read.All (Admin Only)'

My PowerShell:
$AppID = 'AzureAppGUID'
$AppSecret = ConvertTo-SecureString -String 'AzureAppPassword' -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($AppID,$AppSecret)
$AppParams = @{
Name = 'MSGraph'
ClientCredential = $Credential
RedirectUri = 'https://localhost/'
Tenant = 'tenant.onmicrosoft.com'
}
$App = New-GraphApplication @AppParams
$AuthCode = Get-GraphOauthAuthorizationCode -Application $App
$AccessToken = Get-GraphOauthAccessToken -AuthenticationCode $AuthCode

Everything runs fine, but $AccessToken is expired when I create it and I can't use it for anything. Example:
GUID : TokenGUID
RequestedDate : 10/3/2018 4:24:50 PM
LastRequestDate : 10/3/2018 4:24:50 PM
IsExpired : True
Expires : 10/3/2018 4:24:51 PM
NotBefore : 10/3/2018 3:19:51 PM
Scope : {Mail.ReadWrite, User.Read}
Resource : https://graph.microsoft.com
IsRefreshable : True
Application : Guid: AppGUID Name: MSGraph

Interacting with the token by exporting it to a file, importing it from a file, and refreshing has no effect on the expiration.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.