microsoft-365/AddSharepointPerms.ps1

# & ([scriptblock]::Create((irm -Uri "https://raw.githubusercontent.com/J-Ranck-Electric/powershell-scripts/main/AddSharepointPerms.ps1" -Headers @{ Authorization = "token $token" }))) -LibraryName "Jobs" -RootFolder "SSM" -TargetFolderName "Contract Documents" -MaxDepth 2 -Recipients "owitbeck@jranck.com" -Roles "write" -WhatIf

[CmdletBinding()]
param (
    [Parameter(Mandatory = $true)]
    [string]$LibraryName,

    [Parameter(Mandatory = $false)]
    [string]$RootFolder = "",

    [Parameter(Mandatory = $true)]
    [string]$TargetFolderName,

    [Parameter(Mandatory = $false)]
    [int]$MaxDepth = [int]::MaxValue,

    [Parameter(Mandatory = $true)]
    [string[]]$Recipients,

    [Parameter(Mandatory = $true)]
    [ValidateSet("read", "write", "owner")]
    [string[]]$Roles,
    
    [Parameter(Mandatory = $false)]
    [switch]$WhatIf
)

# Function to ensure required modules are installed
function Test-AndInstallModule {
    param (
        [Parameter(Mandatory = $true)]
        [string]$ModuleName
    )
    
    if (-not (Get-Module -Name $ModuleName -ListAvailable)) {
        Write-Host "Module $ModuleName is not installed. Installing now..." -ForegroundColor Yellow
        try {
            Install-Module -Name $ModuleName -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop
            Write-Host "Module $ModuleName installed successfully." -ForegroundColor Green
        }
        catch {
            Write-Error "Failed to install module $ModuleName. Error: $_"
            exit 1
        }
    }
    
    # Import the module
    try {
        Import-Module -Name $ModuleName -ErrorAction Stop
    }
    catch {
        Write-Error "Failed to import module $ModuleName. Error: $_"
        exit 1
    }
}

# Install and import required modules
$requiredModules = @("Microsoft.Graph.Sites", "Microsoft.Graph.Authentication")
foreach ($module in $requiredModules) {
    Test-AndInstallModule -ModuleName $module
}

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Sites.Read.All, Files.ReadWrite.All" -NoWelcome

# Format recipients for sharing parameters
$recipientsArray = @()
foreach ($recipient in $Recipients) {
    $recipientsArray += @{ email = $recipient }
}

# Prepare sharing parameters
$sharingParams = @{
    requireSignIn = $true
    recipients    = $recipientsArray
    roles         = $Roles
}

# Get site ID using Invoke-MgGraphRequest
$siteUrl = "https://graph.microsoft.com/v1.0/sites/jranck.sharepoint.com:/sites/I-Drive"
$siteResponse = Invoke-MgGraphRequest -Uri $siteUrl -Method GET
$siteId = $siteResponse.id

# Get all drives (document libraries) in the site using Invoke-MgGraphRequest
$drivesUrl = "https://graph.microsoft.com/v1.0/sites/$siteId/drives"
$drivesResponse = Invoke-MgGraphRequest -Uri $drivesUrl -Method GET
$drives = $drivesResponse.value

# Find the specific library you want to work with
$drive = $drives | Where-Object Name -eq $LibraryName
if ($null -eq $drive) {
    Write-Host "Library '$LibraryName' not found. Available libraries:" -ForegroundColor Red
    $drives | Select-Object Name, Id | Format-Table
    exit
}
$driveId = $drive.id

# Initialize array to store found folders
$script:foundFolders = @()

# Function to get folder by path using Invoke-MgGraphRequest
function Get-FolderByPath {
    param(
        [string]$FolderPath
    )
    
    try {
        $encodedPath = [System.Web.HttpUtility]::UrlEncode($FolderPath)
        $folderUrl = "https://graph.microsoft.com/v1.0/drives/$driveId/root:/$encodedPath"
        $folderResponse = Invoke-MgGraphRequest -Uri $folderUrl -Method GET
        return $folderResponse
    }
    catch {
        Write-Host "Error getting folder at path $FolderPath : $_" -ForegroundColor Red
        return $null
    }
}

# Function to search folders at specific depth level
function Search-FoldersAtLevel {
    param(
        [Parameter(Mandatory = $true)]
        [string]$FolderId,
        
        [Parameter(Mandatory = $true)]
        [string]$FolderPath,
        
        [Parameter(Mandatory = $true)]
        [int]$TargetDepth,
        
        [Parameter(Mandatory = $true)]
        [int]$CurrentDepth
    )
    
    # If we've reached target depth, check for target folders
    if ($CurrentDepth -eq $TargetDepth) {
        Write-Host "Searching at depth ${CurrentDepth}: $FolderPath" -ForegroundColor Cyan
        
        # Get all folders at this level
        $foldersUrl = "https://graph.microsoft.com/v1.0/drives/$driveId/items/$FolderId/children?`$filter=folder ne null"
        $foldersResponse = Invoke-MgGraphRequest -Uri $foldersUrl -Method GET
        $folders = $foldersResponse.value
        
        # Check for target folders
        foreach ($folder in $folders) {
            if ($folder.name -eq $TargetFolderName) {
                $currentPath = "$FolderPath/$($folder.name)"
                Write-Host "Found target folder: $currentPath" -ForegroundColor Green
                $script:foundFolders += [PSCustomObject]@{
                    Name         = $folder.name
                    Path         = $currentPath
                    ItemId       = $folder.id
                    CreatedDate  = $folder.createdDateTime
                    LastModified = $folder.lastModifiedDateTime
                    Depth        = $CurrentDepth
                }
            }
        }
        return
    }
    
    # If not at target depth yet, get folders and continue recursion
    $foldersUrl = "https://graph.microsoft.com/v1.0/drives/$driveId/items/$FolderId/children?`$filter=folder ne null"
    $foldersResponse = Invoke-MgGraphRequest -Uri $foldersUrl -Method GET
    $folders = $foldersResponse.value
    
    foreach ($folder in $folders) {
        Search-FoldersAtLevel -FolderId $folder.id -FolderPath "$FolderPath/$($folder.name)" -TargetDepth $TargetDepth -CurrentDepth ($CurrentDepth + 1)
    }
}

try {
    # Get the starting folder using Invoke-MgGraphRequest
    if ([string]::IsNullOrEmpty($RootFolder)) {
        $rootFolderUrl = "https://graph.microsoft.com/v1.0/drives/$driveId/root"
        $rootFolderResponse = Invoke-MgGraphRequest -Uri $rootFolderUrl -Method GET
        $rootFolderItem = $rootFolderResponse
        $rootFolderPath = "/$LibraryName"
    }
    else {
        $rootFolderUrl = "https://graph.microsoft.com/v1.0/drives/$driveId/root:/$RootFolder"
        $rootFolderResponse = Invoke-MgGraphRequest -Uri $rootFolderUrl -Method GET
        $rootFolderItem = $rootFolderResponse
        $rootFolderPath = "/$LibraryName/$RootFolder"
    }
    
    # Search for target folders at each depth level
    Write-Host "Starting search for '$TargetFolderName' folders in '$rootFolderPath' up to depth $MaxDepth" -ForegroundColor Yellow
    
    # Check if the root folder itself matches target name
    if ($rootFolderItem.name -eq $TargetFolderName) {
        Write-Host "Found target folder at root level: $rootFolderPath" -ForegroundColor Green
        $script:foundFolders += [PSCustomObject]@{
            Name         = $rootFolderItem.name
            Path         = $rootFolderPath
            ItemId       = $rootFolderItem.id
            CreatedDate  = $rootFolderItem.createdDateTime
            LastModified = $rootFolderItem.lastModifiedDateTime
            Depth        = 0
        }
    }
    
    # Search at each depth level from 1 to maxDepth
    for ($depth = 1; $depth -le $MaxDepth; $depth++) {
        Write-Host "`nSearching at depth level $depth..." -ForegroundColor Magenta
        Search-FoldersAtLevel -FolderId $rootFolderItem.id -FolderPath $rootFolderPath -TargetDepth $depth -CurrentDepth 1
    }
    
    # Display all found folders
    Write-Host "`nAll '$TargetFolderName' folders found: $($script:foundFolders.Count) folders" -ForegroundColor Yellow
    $script:foundFolders | Sort-Object Path | Format-Table Name, Path, Depth, CreatedDate, LastModified -AutoSize

    # Share each found folder with the specified user
    Write-Host "`nSharing folders with recipients: $($Recipients -join ', ')..." -ForegroundColor Yellow

    # Initialize counters for summary
    $successCount = 0
    $failCount = 0
    $whatIfCount = 0
    $errorFolders = @()

    foreach ($folder in $script:foundFolders) {
        try {
            Write-Host "Sharing folder: $($folder.Path)" -ForegroundColor Cyan

            if ($WhatIf) {
                Write-Host "WhatIf: Sharing would be done here for folder: $($folder.Path)" -ForegroundColor Yellow
                $whatIfCount++
            }
            else {
                Invoke-MgInviteDriveItem -DriveId $driveId -DriveItemId $folder.ItemId -BodyParameter $sharingParams
                Write-Host "Successfully shared folder: $($folder.Path)" -ForegroundColor Green
                $successCount++
            }
        }
        catch {
            Write-Host "Error sharing folder $($folder.Path): $_" -ForegroundColor Red
            $failCount++
            $errorFolders += $folder.Path
        }
    }

    # Display summary
    Write-Host "`n===== SHARING SUMMARY =====" -ForegroundColor Cyan
    if ($WhatIf) {
        Write-Host "WhatIf mode: $whatIfCount folders would be shared" -ForegroundColor Yellow
    } else {
        Write-Host "Successfully shared: $successCount folders" -ForegroundColor Green
        Write-Host "Failed to share: $failCount folders" -ForegroundColor $(if($failCount -gt 0){"Red"}else{"Green"})
    }
    
    if ($failCount -gt 0) {
        Write-Host "`nFolders that failed to share:" -ForegroundColor Red
        $errorFolders | ForEach-Object { Write-Host "- $_" -ForegroundColor Red }
    }
    Write-Host "=========================" -ForegroundColor Cyan
}
catch {
    Write-Host "Error: $_" -ForegroundColor Red
    Write-Host "Please check if the library and folder path exist." -ForegroundColor Yellow
}