Microsoft Graph Api
5 TopicsGraph API for accessing Mail Info
Hi PowerShell Community, I need to access the info below Total Numbers of email by each mailbox Total Attachments per email by each mailbox Type of attachments (PDF, Word, Excel, PPT, Image, Etc..) Total email, each mailbox Last access For which i need to know which Graph API I need for the info mentioned above. Can someone please help us in finding this.138Views0likes0CommentsPowerShell - GraphAPI - OneDrive Shared files - Error
Hello everybody, for changing the UPN for some number of users I need to export all the shared files in OneDrive for all users in the tenant. I have found an excellent script by the MVP Vasil Michev on the GitHub (PowerShell/Graph_ODFB_shared_files.ps1 at master · michevnew/PowerShell · GitHub). The script uses Entra ID App Registration with API permissions, GraphAPI and Powershell. I needed to change the script because it didn't export all the files, just some of them and i wanted to have a .csv export. After I changed the script, I get all the shared files, but the scripts stops after exporting the files from about half of my users (stops by the letter m in the UPN). This is the error that I am getting (for all the user until m in UPN the results are ok): ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null. At C:\scripts\OneDrive_revised3.ps1:255 char:30 + return $result.Content | ConvertFrom-Json Is it an issue with a GraphAPI limitation (to many requests) or something else? How can I resolve this? Can I use a list of some users (from a csv file) to export the data only for these users? Here is the moddified script (I deleted Entra ID secrets and IDs): [CmdletBinding()] #Make sure we can use -Verbose Param([switch]$ExpandFolders,[int]$depth) function processChildren { Param( #Graph User object [Parameter(Mandatory=$true)]$User, #URI for the drive [Parameter(Mandatory=$true)][string]$URI, #Use the ExpandFolders switch to specify whether to expand folders and include their items in the output. [switch]$ExpandFolders, #Use the Depth parameter to specify the folder depth for expansion/inclusion of items. [int]$depth) $URI = "$URI/children" $children = @() #fetch children, make sure to handle multiple pages do { $result = Invoke-GraphApiRequest -Uri "$URI" -Verbose:$VerbosePreference $URI = $result.'@odata.nextLink' #If we are getting multiple pages, add some delay to avoid throttling Start-Sleep -Milliseconds 500 $children += $result } while ($URI) if (!$children) { Write-Verbose "No items found for $($user.userPrincipalName), skipping..."; continue } #handle different children types $output = @() $cFolders = $children.value | ? {$_.Folder} $cFiles = $children.value | ? {$_.File} #doesnt return notebooks $cNotebooks = $children.value | ? {$_.package.type -eq "OneNote"} #Process Folders foreach ($folder in $cFolders) { $output += (processFolder -User $User -folder $folder -ExpandFolders:$ExpandFolders -depth $depth -Verbose:$VerbosePreference) } #Process Files foreach ($file in $cFiles) { if ($file.shared) { $output += (processFile -User $User -file $file -Verbose:$VerbosePreference) } } #Process Notebooks foreach ($notebook in $cNotebooks) { if ($notebook.shared) { $output += (processFile -User $User -file $notebook -Verbose:$VerbosePreference) } } return $output } function processFolder { Param( #Graph User object [Parameter(Mandatory=$true)]$User, #Folder object [Parameter(Mandatory=$true)]$folder, #Use the ExpandFolders switch to specify whether to expand folders and include their items in the output. [switch]$ExpandFolders, #Use the Depth parameter to specify the folder depth for expansion/inclusion of items. [int]$depth) #prepare the output object $fileinfo = New-Object psobject $fileinfo | Add-Member -MemberType NoteProperty -Name "OneDriveOwner" -Value $user.userPrincipalName $fileinfo | Add-Member -MemberType NoteProperty -Name "Name" -Value $folder.name $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemType" -Value "Folder" $fileinfo | Add-Member -MemberType NoteProperty -Name "Shared" -Value (&{If($folder.shared) {"Yes"} Else {"No"}}) #if the Shared property is set, fetch permissions if ($folder.shared) { $permlist = getPermissions $user.id $folder.id -Verbose:$VerbosePreference #Match user entries against the list of domains in the tenant to populate the ExternallyShared value $regexmatches = $permlist | % {if ($_ -match "\(?\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\)?") {$Matches[0]}} if ($permlist -match "anonymous") { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "Yes" } else { if (!$domains) { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "No domain info" } elseif ($regexmatches -notmatch ($domains -join "|")) { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "Yes" } else { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "No" } } $fileinfo | Add-Member -MemberType NoteProperty -Name "Permissions" -Value ($permlist -join ",") } $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemPath" -Value $folder.webUrl #Since this is a folder item, check for any children, depending on the script parameters if (($folder.folder.childCount -gt 0) -and $ExpandFolders -and ((3 - $folder.parentReference.path.Split("/").Count + $depth) -gt 0)) { Write-Verbose "Folder $($folder.Name) has child items" $uri = "https://graph.microsoft.com/v1.0/users/$($user.id)/drive/items/$($folder.id)" $folderItems = processChildren -User $user -URI $uri -ExpandFolders:$ExpandFolders -depth $depth -Verbose:$VerbosePreference } #handle the output if ($folderItems) { $f = @(); $f += $fileinfo; $f += $folderItems; return $f } else { return $fileinfo } } function processFile { Param( #Graph User object [Parameter(Mandatory=$true)]$User, #File object [Parameter(Mandatory=$true)]$file) #prepare the output object $fileinfo = New-Object psobject $fileinfo | Add-Member -MemberType NoteProperty -Name "OneDriveOwner" -Value $user.userPrincipalName $fileinfo | Add-Member -MemberType NoteProperty -Name "Name" -Value $file.name $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemType" -Value (&{If($file.package.Type -eq "OneNote") {"Notebook"} Else {"File"}}) $fileinfo | Add-Member -MemberType NoteProperty -Name "Shared" -Value (&{If($file.shared) {"Yes"} Else {"No"}}) #if the Shared property is set, fetch permissions if ($file.shared) { $permlist = getPermissions $user.id $file.id -Verbose:$VerbosePreference #Match user entries against the list of domains in the tenant to populate the ExternallyShared value $regexmatches = $permlist | % {if ($_ -match "\(?\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\)?") {$Matches[0]}} if ($permlist -match "anonymous") { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "Yes" } else { if (!$domains) { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "No domain info" } elseif ($regexmatches -notmatch ($domains -join "|")) { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "Yes" } else { $fileinfo | Add-Member -MemberType NoteProperty -Name "ExternallyShared" -Value "No" } } $fileinfo | Add-Member -MemberType NoteProperty -Name "Permissions" -Value ($permlist -join ",") } $fileinfo | Add-Member -MemberType NoteProperty -Name "ItemPath" -Value $file.webUrl #handle the output return $fileinfo } function getPermissions { Param( #Use the UserId parameter to provide an unique identifier for the user object. [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$UserId, #Use the ItemId parameter to provide an unique identifier for the item object. [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$ItemId) #fetch permissions for the given item $uri = "https://graph.microsoft.com/beta/users/$($UserId)/drive/items/$($ItemId)/permissions" $permissions = (Invoke-GraphApiRequest -Uri $uri -Verbose:$VerbosePreference).Value #build the permissions string $permlist = @() foreach ($entry in $permissions) { #Sharing link if ($entry.link) { $strPermissions = $($entry.link.type) + ":" + $($entry.link.scope) if ($entry.grantedToIdentitiesV2) { $strPermissions = $strPermissions + " (" + (((&{If($entry.grantedToIdentitiesV2.siteUser.email) {$entry.grantedToIdentitiesV2.siteUser.email} else {$entry.grantedToIdentitiesV2.User.email}}) | select -Unique) -join ",") + ")" } if ($entry.hasPassword) { $strPermissions = $strPermissions + "[PasswordProtected]" } if ($entry.link.preventsDownload) { $strPermissions = $strPermissions + "[BlockDownloads]" } if ($entry.expirationDateTime) { $strPermissions = $strPermissions + " (Expires on: $($entry.expirationDateTime))" } $permlist += $strPermissions } #Invitation elseif ($entry.invitation) { $permlist += $($entry.roles) + ":" + $($entry.invitation.email) } #Direct permissions elseif ($entry.roles) { if ($entry.grantedToV2.siteUser.Email) { $roleentry = $entry.grantedToV2.siteUser.Email } elseif ($entry.grantedToV2.User.Email) { $roleentry = $entry.grantedToV2.User.Email } #else { $roleentry = $entry.grantedToV2.siteUser.DisplayName } else { $roleentry = $entry.grantedToV2.siteUser.loginName } #user claim $permlist += $($entry.Roles) + ':' + $roleentry #apparently the email property can be empty... } #Inherited permissions elseif ($entry.inheritedFrom) { $permlist += "[Inherited from: $($entry.inheritedFrom.path)]" } #Should have a Roles facet, thus covered above #some other permissions? else { Write-Verbose "Permission $entry not covered by the script!"; $permlist += $entry } } #handle the output return $permlist } function Renew-Token { #prepare the request $url = 'https://login.microsoftonline.com/' + $tenantId + '/oauth2/v2.0/token' $Scopes = New-Object System.Collections.Generic.List[string] $Scope = "https://graph.microsoft.com/.default" $Scopes.Add($Scope) $body = @{ grant_type = "client_credentials" client_id = $appID client_secret = $client_secret scope = $Scopes } try { Set-Variable -Name authenticationResult -Scope Global -Value (Invoke-WebRequest -Method Post -Uri $url -Debug -Verbose -Body $body -ErrorAction Stop) $token = ($authenticationResult.Content | ConvertFrom-Json).access_token } catch { $_; return } if (!$token) { Write-Host "Failed to aquire token!"; return } else { Write-Verbose "Successfully acquired Access Token" #Use the access token to set the authentication header Set-Variable -Name authHeader -Scope Global -Value @{'Authorization'="Bearer $token";'Content-Type'='application\json'} } } function Invoke-GraphApiRequest { param( [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][string]$Uri ) if (!$AuthHeader) { Write-Verbose "No access token found, aborting..."; throw } try { $result = Invoke-WebRequest -Headers $AuthHeader -Uri $uri -Verbose:$VerbosePreference -ErrorAction Stop } catch [System.Net.WebException] { if ($_.Exception.Response -eq $null) { throw } #Get the full error response $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream()) $streamReader.BaseStream.Position = 0 $errResp = $streamReader.ReadToEnd() | ConvertFrom-Json $streamReader.Close() if ($errResp.error.code -match "ResourceNotFound|Request_ResourceNotFound") { Write-Verbose "Resource $uri not found, skipping..."; return } #404, continue #also handle 429, throttled (Too many requests) elseif ($errResp.error.code -eq "BadRequest") { return } #400, we should terminate... but stupid Graph sometimes returns 400 instead of 404 elseif ($errResp.error.code -eq "Forbidden") { Write-Verbose "Insufficient permissions to run the Graph API call, aborting..."; throw } #403, terminate elseif ($errResp.error.code -eq "InvalidAuthenticationToken") { if ($errResp.error.message -eq "Access token has expired.") { #renew token, continue Write-Verbose "Access token has expired, trying to renew..." Renew-Token if (!$AuthHeader) { Write-Verbose "Failed to renew token, aborting..."; throw } #Token is renewed, retry the query $result = Invoke-GraphApiRequest -Uri $uri -Verbose:$VerbosePreference } } else { Write-Verbose "Unexpected error: $errResp"; return } } #handle the output return $result.Content | ConvertFrom-Json } #Main script body #Make sure to define all the required variables $tenantId = "" $appID = "" $client_secret = "" #Use the ExpandFolders switch to specify whether to expand folders and include their items in the output. $ExpandFolders = $true #Use the Depth parameter to specify the folder depth for expansion/inclusion of items. $depth = 5 #Define the list of domains in the tenant $domains = @("contoso.com", "example.com") #Renew the token to ensure it's valid Renew-Token #Get all users in the tenant $users = Invoke-GraphApiRequest -Uri "https://graph.microsoft.com/v1.0/users?`$top=999" -Verbose:$VerbosePreference #Process each user foreach ($user in $users.value) { Write-Host "Processing $($user.userPrincipalName) OneDrive..." #Define the URI for the user's drive $uri = "https://graph.microsoft.com/v1.0/users/$($user.id)/drive/root" #Fetch items for the user's drive $items = processChildren -User $user -URI $uri -ExpandFolders:$ExpandFolders -depth $depth #Output the items for the user $sharedItems = $items | Where-Object { $_.Shared -eq "Yes" } if ($sharedItems.Count -gt 0) { $sharedItems | Export-Csv -Path "C:\temp\$($user.userPrincipalName)_OneDriveItems.csv" -NoTypeInformation -Force } } Write-Host "Export completed." Kindest regards, Leon Pavesic (LinkedIn) (Twitter)Solved1.1KViews0likes3CommentsMicrosoft Graph PowerShell SDK Module OneDrive Folder Permissions Assignment
As an M365 Global Admin, I have been tasked with creating a new folder in other users OneDrive root folder (Documents) we can call that folder 'myFolder', then I need to assign a Microsoft Azure Security group ('myGroup') to that folder with read/write permissions. I have a list of users (taking input from txt file of UPNs), and am able to loop through the users and create the folder, so step one is possible, but where I am failing is the assignment of the permissions, am using PowerShell 7 and the MS Graph PowerShell Mod, actually for the folder creation I used the API and invoke-method, but I am running into problems since OneDrive is on top of SharePoint, I am not sure how to accomplish this task. Any help would be greatly appreciated! Original Post: https://techcommunity.microsoft.com/t5/microsoft-365/microsoft-graph-powershell-sdk-module-onedrive-folder/m-p/3940795495Views0likes0CommentsFacing issues while using ImportExcel PowerShell module
Hi All, I am trying to export Azure AD group member details to a single excel workbook where every group member details should be mentioned in a separate worksheet. For example, group1 worksheet should contains members of one AAD group and group2 worksheet contains members of another AAD group and so on so forth. For this I am using two PowerShell modules: Microsoft.Graph and ImportExcel. The issue is that once 34 worksheets are added to the excel workbook, I get this error : InvocationInfo : MyCommand : Add-ExcelTable ScriptLineNumber : 428 OffsetInLine : 39 HistoryId : 122 ScriptName : C:\Users\ashish\Documents\PowerShell\Modules\ImportExcel\7.8.4\Public\Export-Excel.ps1 Line : Add-ExcelTable -Range $ws.Cells[$dataRange] -TableName $TableName -TableStyle $TableStyle -TableTotalSettings $TableTotalSettings PositionMessage : At C:\Users\ashish\Documents\PowerShell\Modules\ImportExcel\7.8.4\Public\Export-Excel.ps1:428 char:39 + Add-ExcelTable -Range $ws.Cells[$dataRange] -TableNam … + ~~~~~~~~~~~~~~~~~~~~~ =============================== Below is my code for reference. param( [Parameter(Mandatory, HelpMessage = "Enter the pattern for filtering groups" )] $Pattern ) # Excel file path $ExcelFilePath = "$($PSScriptroot)\GroupMembers.xlsx" # Azure AD App details $ApplicationId = $Env:Azure_CLIENT_ID $TenantID = $Env:Azure_TENANT_ID $ClientSecret = $Env:Azure_CLIENT_SECRET | ConvertTo-SecureString -AsPlainText -Force $ClientSecretCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList $ApplicationId, $ClientSecret # Connecting to Microsoft Graph Connect-MgGraph -TenantId $TenantID -ClientSecretCredential $ClientSecretCredential | Out-Null # Getting all the groups with displayname starting with the provided pattern $Groups = Get-MgGroup -filter "startswith(displayname,'$Pattern')" -Top 2000 # Looping through all the filtered groups and exporting their members to the csv files $Count = 0 foreach ($Group in $Groups) { $Count += 1 $WorkSheetName = "Group$($Count)" Try{ (Get-MgGroupMember -GroupId $Group.id -Top 150).AdditionalProperties | ` Select-Object @{n = "DisplayName"; e = { $_.displayName } }, @{n = "UserprincipalName"; e = { $_.userPrincipalName } } |` Export-Excel -path $ExcelFilePath -WorksheetName $WorkSheetName -Append -TableStyle 'Medium16' ` -Title $Group.Displayname -TitleSize 14 -TitleBold } Catch { Write-Host $_.Exception.Message -ForegroundColor 'Red' Break } } Any help would be appreciated. Regards, Ashish Arya3.4KViews0likes1CommentMicrosoftTeams cmdlets not working with AccessToken
I am trying to run cmdlets from powershell module MicrosoftTeams (version 2.0.0) in a C# web application. I am using Authorization code flow and code from the answer provided in this post to acquire token: Acquire AAD token using ASP.Net web forms. Note: I had changed resource in the code to graph.windows.net to acquire AAD token. Token is acquired by using AuthenticationContext.AcquireTokenByAuthorizationCodeAsync method. Once the token is acquired, I run the following lines to create a powershell instance in C# and to import MicrosoftTeams Module. PowerShell pshell InitialSessionState iss; iss = InitialSessionState.CreateDefault2(); iss.ImportPSModule(new[] { "MicrosoftTeams" }); pshell = PowerShell.Create(iss); Then to connect with MicrosoftTeams, I run the following code: var connectCmd = new Command("Connect-MicrosoftTeams"); connectCmd.Parameters.Add("AadAccessToken", AccessToken); connectCmd.Parameters.Add("AccountId", "xxxxxxx@xxxxxx.onmicrosoft.com"); pshell.Commands.AddCommand(connectCmd); var result1 = pshell.Invoke(); Code works fine till here. After this I clear the shell commands and invoke the Get-CsTeamsCallingPolicy cmdlet: pshell.Commands.Clear(); pshell.Streams.Error.Clear(); pshell.AddScript("Get-CsTeamsCallingPolicy"); var result2 = pshell.Invoke(); After Invoke, I get an exception and this dialog pops up: Pressing 'Continue' brings back the same dialogue a couple of times. Exception details from this screen are: System.Collections.Generic.KeyNotFoundException was unhandled by user code HResult=-2146232969 Message=The given key was not present in the dictionary. Source=mscorlib StackTrace: at System.Collections.Concurrent.ConcurrentDictionary`2.get_Item(TKey key) at Microsoft.TeamsCmdlets.Powershell.Connect.Models.AzureSessionProvider.GetAccessToken(String resource, IEnumerable`1 scopes) in D:\a\1\s\src\Microsoft.TeamsCmdlets.PowerShell.Connect\Models\AzureSession.cs:line 80 at Microsoft.TeamsCmdlets.Powershell.Connect.TeamsPowerShellSession.GetAccessToken(String resource, IEnumerable`1 scopes) in D:\a\1\s\src\Microsoft.TeamsCmdlets.PowerShell.Connect\TeamsPowerShellSession.cs:line 82 at Microsoft.TeamsCmdlets.PowerShell.Connect.GetCsInternalAccessToken.ProcessRecord() in D:\a\1\s\src\Microsoft.TeamsCmdlets.PowerShell.Connect\GetCsInternalAccessToken.cs:line 61 at System.Management.Automation.CommandProcessor.ProcessRecord() After pressing continue for the 3rd time, control goes back to C# code, and I receive the following runtime exception: Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "GetRemoteNewCsOnlineSession" with "1" argument(s): "Run either Connect-MicrosoftTeams or new-csonlinesession before running cmdlets."" Trying to run this logic from the powershell editor shows similar behavior: Running the following two lines: $AccessToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' Connect-MicrosoftTeams -AadAccessToken $AccessToken -AccountId 'xxxxxxx@xxxxxx.onmicrosoft.com' gives this result: Account Environment Tenant TenantId ------- ----------- ------ -------- xxxxxxx@xxxxxx.onmicrosoft.com AzureCloud xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx I then run Get-Team cmdlet: Get-Team -User xxxxxxx@xxxxxxx.onmicrosoft.com which results in this message: Get-Team : The given key was not present in the dictionary. At line:1 char:1 + Get-Team -User xxxxxxx@xxxxxxx.onmicrosoft.com + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Get-Team], KeyNotFoundException + FullyQualifiedErrorId : System.Collections.Generic.KeyNotFoundException,Microsoft.TeamsCmdlets.PowerShell.Custom.GetTeam Running cmdlet Get-CsTeamsCallingPolicy yields this: Exception calling "GetSteppablePipeline" with "1" argument(s): "Exception calling "GetRemoteNewCsOnlineSession" with "1" argument(s): "Run either Connect-MicrosoftTeams or new-csonlinesession before running cmdlets."" At C:\Program Files\WindowsPowerShell\Modules\MicrosoftTeams\2.0.0\net472\SfBORemotePowershellModule.psm1:11369 char:13 + $steppablePipeline = $scriptCmd.GetSteppablePipeline($myI ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException + FullyQualifiedErrorId : CmdletInvocationException If I run Connect-MicrosoftTeams directly from powershell without providing access token and accountid, I get the login screens and after login everything works fine i.e. I can run Get-Team and Get-CsTeamsCallingPolicy cmdlets successfully but I don't get this behavior when working with AadAccessToken. All the above code works fine if connecting to AzureAD module via Connect-AzureAD cmdlet like this both in web application and powershell editor: Connect-AzureAD -AadAccessToken $AccessToken -AccountId 'xxxxxxx@xxxxxxx.onmicrosoft.com' If someone has faced and successfully resolved this issue or have some tips on how to resolve this, please help. I have already tried a lot of things including searching for the specific exception messages and any possible solutions but found nothing that could help in this particular scenario, installed the latest version of MSTeams module, the previous version was old and did not have all the cmdlets that I am looking to work with. I installed the preview version of MSTeams module also to see if this issue is fixed in the upcoming release. Uninstalled the deprecated SkypeForBuisnessOnline Connector module, updated windows and so on. If you look at Example 4 in the Microsoft documentation for Connect-MicrosoftTeams, this is what I am trying to achieve.2.1KViews1like5Comments