"Microsoft Teams Powershell"
3 TopicsTeams Powershell Module: [Get|Set|New|Sync]-CsOnlineApplicationInstance and App-Based authentication
Hello there, as stated in the documentation: https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-application-authentication it is not possible to create a new teams application using New-CsOnlineApplicationInstance, if application based authentication is used for the PS Module. Is this planed and/or on the roadmap to implement this? We would desperately need this. Thanks in advance!Solved1.1KViews0likes10CommentsPowerShell - 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.1KViews0likes3CommentsUnable to Execute PowerShell Script Commands in Microsoft Teams Session Established via Script
I encountered an issue while attempting to execute PowerShell script commands within a Microsoft Teams session established via a script. The script includes commands to connect to Microsoft Teams using the Connect-MicrosoftTeams cmdlet and subsequently execute other Teams-related commands. While the script executes without errors, the session does not seem to be fully established, resulting in the following error when attempting to execute subsequent commands: powershell : Get-CsTeamsClientConfiguration : Session is not established, run Connect-MicrosoftTeams before requesting access token At line:1 char:1 + powershell -File 'C:\Users\***********************\script.p ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (Get-CsTeamsClie...ng access token:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError At C:\Users\****************************\script.ps1:8 char:1 + Get-CsTeamsClientConfiguration + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [Get-CsTeamsClientConfiguration], UnauthorizedAccessException + FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.Teams.ConfigApi.Cmdlets.GetCsTeamsClientCon figuration This issue is inconsistent, as the commands execute successfully when executed manually. Additionally, I've attempted to introduce delays in the script to allow for the session to fully establish, but the issue persists. This issue impacts the ability to automate tasks in Microsoft Teams using PowerShell scripts. Please let me know if there are any additional steps or information needed to address this issue effectively.1.1KViews0likes2Comments