markekraus / psmsgraph Goto Github PK
View Code? Open in Web Editor NEWA PowerShell module for the Microsoft Graph API
License: MIT License
A PowerShell module for the Microsoft Graph API
License: MIT License
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 |
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?
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 |
You use $Global:MSGraphAPICurrentUri
in that function, can you use $Script:
scope instead? I think that should be global to all the scripts in your module (but I have not tested it).
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 |
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 |
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() |
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' |
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' |
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 |
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!
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 |
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 :)
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' |
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 |
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 |
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 |
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 |
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.
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 |
Paging is a well described core part of graph API and should be part of the Invoke-GraphRequest function. If you're ok with this functionality, I am willing to add it.
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 |
Code coverage report:
Covered 100 % of 9 analyzed Commands in New-GraphApplication
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 |
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 |
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 |
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
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.
Similar to #31, I am running into the this problem suddenly
PSMSGraph Version: 1.0.26.43
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.
Code coverage report:
Covered 100 % of 12 analyzed Commands in New-GraphOauthAccessToken
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
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." |
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!
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!!!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.