diff options
| author | Pascal Dulieu <pascal@dulieu.uk> | 2026-04-22 12:15:30 +0100 |
|---|---|---|
| committer | Pascal Dulieu <pascal@dulieu.uk> | 2026-04-22 12:15:30 +0100 |
| commit | 08b95769a59e44d746cc8e228beef16e8d91a365 (patch) | |
| tree | df5f0410ab4ddb28d2e2e6e5e5b98bb5ea9ee146 | |
| parent | 4fe1417944a536acf3fcf5aa881a6a99fc26c16b (diff) | |
| -rw-r--r-- | README.md | 11 | ||||
| -rw-r--r-- | T2R.ps1 | 156 | ||||
| -rw-r--r-- | config.json | 5 | ||||
| -rw-r--r-- | remote-template.conf | 1 |
4 files changed, 126 insertions, 47 deletions
@@ -13,8 +13,8 @@ Becuase microsoft dont like to make things easy and create sharepoint subsites f 3. Once the app has been made make note of the Application (client) ID and Directory (tennant) ID 4. Go to Certificates & Secrets > Client secrets and create a new client secret, set the expire and click add 5. Once it has been generated make a note of the Value, once you change page that will be stared out and you wont be able to see it again. -6. In your T2R.ps1 set the `tennantId`, `clientID` and `client secret (Value)` -7. in the remote-template.conf set the `client ID`, `client secret (value)` and `tennantID` +6. In your config.json set the `tennantId`, `clientID` and `client secret (Value)` +7. in the remote-template.conf set the `client ID`, `client secret (value)`, `tennantID` and replace `<tenant>` with your tenantID 8. Go to `API permissions > Add a permission > Microsoft Graph > Application permissions` and add the below permissions ```plain @@ -32,13 +32,10 @@ Team.ReadBasic.All Sites.Read.All ``` -1. Once the permissions have been set and your remote-template.conf and the top 2 lines of the T2R.ps1 is filled in, run T2R.ps1 and enter your team name -2. Wait a few seconds for it to do its thing and you should see rclone.conf show up -3. run `rclone lsd --config ./rclone TEAM_NAME_union:` to see a list of the root folders for the team it has pulled - ## Usage - Run the script - Enter your team name when it asks - Wait -- Run `rclone lsd --config ./rclone TEAM_NAME_union:` when the script is finished +- Run `rclone lsd --config ./rclone TEAM_NAME_union:` when the script is finished to confirm you can see everything +- If the team only contains standard channels it will create a config with 1 remote @@ -1,10 +1,17 @@ -$tenantId = "00000000-0000-0000-0000-000000000000" -$clientID = "00000000-0000-0000-0000-000000000000" -$clientSecret = (ConvertTo-SecureString "CLIENT_SECRET_VALUE" -AsPlainText -Force ) +param( + [Parameter(Mandatory = $true)] + [string[]]$Team, + [switch]$Exact +) + +$config = Get-Content -Path ./config.json -Raw | ConvertFrom-Json +$tenantId = $config.tenantId +$clientID = $config.clientID +$clientSecret = (ConvertTo-SecureString $config.clientSecret -AsPlainText -Force ) $Scope = "https://graph.microsoft.com/.default" $authToken = Get-MsalToken -ClientId $clientID -ClientSecret $clientSecret -TenantId $tenantId -Scopes $Scope -$TeamsTeam = Read-Host "Enter team name" +Write-Host "Processing $($Team.Count) teams: $($Team -join ', ')" $Headers = @{ "Authorization" = "Bearer $($authToken.AccessToken)" @@ -12,48 +19,119 @@ $Headers = @{ } $allTeams = @() -$aadTeams = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/teams/" -Headers $Headers -Method Get -ContentType "application/json") +$teamsResponse = Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/teams/" -Headers $Headers -Method GET +$aadTeams = $teamsResponse.Content | ConvertFrom-Json $allTeams += $aadTeams.value if ($aadTeams.'@odata.nextLink') { do { - $aadTeams = (Invoke-RestMethod -Uri $aadTeams.'@odata.nextLink' -Headers $Headers -Method Get -ContentType "application/json") + $teamsResponse = Invoke-WebRequest -Uri $aadTeams.'@odata.nextLink' -Headers $Headers -Method GET + $aadTeams = $teamsResponse.Content | ConvertFrom-Json $allTeams += $aadTeams.value } until (!$aadTeams.'@odata.nextLink') } $aadTeams = $allTeams -$channels = [System.Collections.ArrayList]::New() - -foreach ($teams in $aadTeams) { - if ($teams.displayName -like "$($TeamsTeam)*") { - $apiUri = "https://graph.microsoft.com/v1.0/teams/$($teams.id)/channels" - $Team = Invoke-RestMethod -Headers $Headers -Uri $apiUri -Method GET - $channelList = $Team.Value - foreach ($channel in $channelList) { - $channelFolder = Invoke-RestMethod -Headers $Headers -Uri "https://graph.microsoft.com/v1.0/teams/$($teams.id)/channels/$($channel.id)/filesFolder" -Method GET - $DriveID = $channelFolder.parentReference.driveId - $displayName = $channelFolder.name -replace " ", "_" -replace ",", "" - $Result = [PSCustomObject]@{ - displayName = $displayName - driveID = $DriveID - membershipType = $channel.membershipType - teamName = $teams.displayName +foreach ($requestedTeam in $Team) { + Write-Host "`nProcessing team: $requestedTeam" + + $channels = [System.Collections.ArrayList]::New() + $foundTeam = $false + + foreach ($teams in $aadTeams) { + if ($foundTeam) { break } + + $teamMatches = if ($Exact) { + $teams.displayName -eq $requestedTeam + } else { + $teams.displayName -like "$($requestedTeam)*" + } + + if ($teamMatches) { + Write-Host "Found matching team: $($teams.displayName)" + $apiUri = "https://graph.microsoft.com/v1.0/teams/$($teams.id)/channels" + + $response = Invoke-WebRequest -Headers $Headers -Uri $apiUri -Method GET + + try { + $teamResponse = $response.Content | ConvertFrom-Json -ErrorAction Stop + } catch { + $teamResponse = [System.Web.HttpUtility]::JavaScriptStringDeserializer.Deserialize($response.Content, [System.Object]) + } + + $channelList = $null + + if ($teamResponse.value) { + $channelList = $teamResponse.value + } elseif ($teamResponse.PSObject.Properties['value']) { + $channelList = $teamResponse.PSObject.Properties['value'].Value + } else { + $rawContent = $response.Content + if ($rawContent -match '"value":\s*\[(.*?)\]') { + $jsonArray = "[$($matches[1])]" + $channelList = $jsonArray | ConvertFrom-Json + } + } + + if ($channelList) { + Write-Host "Processing $($channelList.Count) channels for team: $($teams.displayName)" + foreach ($channel in $channelList) { + Start-Sleep -Milliseconds 250 + $folderResponse = Invoke-WebRequest -Headers $Headers -Uri "https://graph.microsoft.com/v1.0/teams/$($teams.id)/channels/$($channel.id)/filesFolder" -Method GET + $channelFolder = $folderResponse.Content | ConvertFrom-Json + $DriveID = $channelFolder.parentReference.driveId + $displayName = $channelFolder.name -replace " ", "_" -replace ",", "" + $Result = [PSCustomObject]@{ + displayName = $displayName + driveID = $DriveID + membershipType = $channel.membershipType + teamName = $teams.displayName + } + [void]$channels.Add($Result) + Write-Host "Added channel: $displayName with DriveID: $DriveID" + } + } else { + Write-Host "WARNING: No channels found for team: $($teams.displayName)" } - [void]$channels.Add($Result) + $foundTeam = $true + } + } + + if ($channels.Count -eq 0) { + Write-Host "ERROR: No channels were processed for team: $requestedTeam" + continue + } + + Write-Host "Total channels processed for $requestedTeam`: $($channels.Count)" + + $uniqueDrives = $channels | Sort-Object -Property driveID -Unique | Select-Object displayName, driveID, membershipType, teamName + Write-Host "Unique drives found for $requestedTeam`: $($uniqueDrives.Count)" + + $uniqueDrives | ForEach-Object { + if ($_.membershipType -eq 'standard') { + $_.displayName = $_.teamName -replace " ", "_" } - } -} - -$uniqueDrives = $channels | Sort-Object -Property driveID -Unique | Select-Object displayName, driveID, membershipType, teamName -$uniqueDrives | ForEach-Object { if ($_.membershipType -eq 'standard') { $_.displayName = $_.teamName -replace " ", "_" } } -foreach ($drive in $uniqueDrives) { - $config = Get-Content -Path ./remote-template.conf -Raw - $remote = $config -f $drive.displayName, $drive.driveID - Add-Content -Value $remote -Path ./rclone.conf -} - -$teamNames = $($uniqueDrives.displayName -join [Environment]::NewLine) -replace "\n", ": " -$teamNames = $teamNames + ":" -$union = Get-Content -Path ./union-template.conf -Raw -$unionConf = $union -f "$($channels[0].teamName)_union", $teamNames -Add-Content -Value $unionConf -Path ./rclone.conf
\ No newline at end of file + } + + $UnionName = "$($channels[0].teamName)_union" -replace " ", "_" + $RcloneFileName = $UnionName -replace "_union", "" + + foreach ($drive in $uniqueDrives) { + $config = Get-Content -Path ./remote-template.conf -Raw + $remote = $config -f $drive.displayName, $drive.driveID + Add-Content -Value $remote -Path "./$RcloneFileName.conf" + Write-Host "Added remote config for: $($drive.displayName)" + } + + if ($uniqueDrives.Count -ge 2) { + $teamNames = $($uniqueDrives.displayName -join [Environment]::NewLine) -replace "\n", ": " + $teamNames = "$teamNames" + ":" + $union = Get-Content -Path ./union-template.conf -Raw + $unionConf = $union -f $UnionName, $teamNames + Add-Content -Value $unionConf -Path "./$RcloneFileName.conf" + Write-Host "Generated union config: $UnionName" + } else { + Write-Host "Skipping union creation - only $($uniqueDrives.Count) remote(s) found (union requires 2+)" + } + + Write-Host "Generated config file: $RcloneFileName.conf" +}
\ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..5d5646b --- /dev/null +++ b/config.json @@ -0,0 +1,5 @@ +{ + "tenantId": "", + "clientID": "", + "clientSecret": "" +}
\ No newline at end of file diff --git a/remote-template.conf b/remote-template.conf index 8918da9..38e77fc 100644 --- a/remote-template.conf +++ b/remote-template.conf @@ -10,4 +10,3 @@ access_scopes = Files.Read Files.Read.All Sites.Read.All offline_access link_scope = organization auth_url = https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize token_url = https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token - |
