From 5a0ccbf57517e0d48727cfc439ff4fde4b5d16b9 Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Sat, 23 Aug 2025 03:40:20 +1000 Subject: [PATCH 01/10] Powershell script to pull tasks from planner --- ...{AuditReport:BAC.md => AuditReport_BAC.md} | 0 .../docs/report-scripts/Planner_Pull.ps1 | 309 ++++++++++++++++++ .../Planner_Pull_Documentation.md | 97 ++++++ src/content/docs/report-scripts/config.txt | 2 + .../docs/report-scripts/ontrack_tasks.csv | 114 +++++++ 5 files changed, 522 insertions(+) rename src/content/docs/BACaudit/{AuditReport:BAC.md => AuditReport_BAC.md} (100%) create mode 100644 src/content/docs/report-scripts/Planner_Pull.ps1 create mode 100644 src/content/docs/report-scripts/Planner_Pull_Documentation.md create mode 100644 src/content/docs/report-scripts/config.txt create mode 100644 src/content/docs/report-scripts/ontrack_tasks.csv diff --git a/src/content/docs/BACaudit/AuditReport:BAC.md b/src/content/docs/BACaudit/AuditReport_BAC.md similarity index 100% rename from src/content/docs/BACaudit/AuditReport:BAC.md rename to src/content/docs/BACaudit/AuditReport_BAC.md diff --git a/src/content/docs/report-scripts/Planner_Pull.ps1 b/src/content/docs/report-scripts/Planner_Pull.ps1 new file mode 100644 index 00000000..4cfb9d64 --- /dev/null +++ b/src/content/docs/report-scripts/Planner_Pull.ps1 @@ -0,0 +1,309 @@ +# Check for Microsoft.Graph module +if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { + Write-Host "Microsoft.Graph module is not installed. Please install it by running: Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force" + exit +} + +# --- Menu Selection --- +Write-Host "How would you like to pull the tasks? Select the relevant Option" +Write-Host "1. Pull all tasks (including unassigned)" +Write-Host "2. Pull only tasks with assigned users ONLY" +Write-Host "3. Pull tasks of assigned users based on a date range(great for progress and handover Doc)" +$selection = Read-Host -Prompt "Enter your choice (1, 2 or 3)" + +if ($selection -ne '1' -and $selection -ne '2' -and $selection -ne '3') { + Write-Host "Invalid selection. Exiting." + exit +} + +if ($selection -eq '3') { + $startDateStr = Read-Host -Prompt "Enter the start date (YYYY-MM-DD)" + $endDateStr = Read-Host -Prompt "Enter the end date (YYYY-MM-DD)" + + try { + $startDate = [datetime]::ParseExact($startDateStr, 'yyyy-MM-dd', $null) + $endDate = [datetime]::ParseExact($endDateStr, 'yyyy-MM-dd', $null).AddDays(1) # Adds one day to include the entire end day + } + catch { + Write-Host "Invalid date format. Please use YYYY-MM-DD. Exiting." + exit + } +} + +# Import required sub modules +Write-Host "Importing Required Microsoft.Graph sub modules..." +Import-Module Microsoft.Graph.Planner +Import-Module Microsoft.Graph.Users + +# Authenticate to Microsoft Graph +Write-Host "Authenticating to Microsoft Graph..." +try { + # Connect to Microsoft Graph with required permissions + Connect-MgGraph -Scopes @( + "Tasks.Read", + "Tasks.ReadWrite", + "User.ReadBasic.All" + ) -UseDeviceCode -Audience "organizations" + + # Verify connection + $context = Get-MgContext + if (-not $context) { + throw "Failed to establish connection" + } + Write-Host "Authentication successful as $($context.Account)" +} +catch { + Write-Error "Authentication failed: $_" + exit 1 +} + +# --- Plan ID Configuration --- +$configPath = ".\config.txt" +$planId = $null + +if (Test-Path $configPath) { + $savedPlans = @(Get-Content $configPath | Where-Object { $_ -match ".+ - .+" }) # Filter for valid entries + if ($savedPlans) { + Write-Host "Please choose a saved Plan ID or enter a new one:" + for ($i = 0; $i -lt $savedPlans.Count; $i++) { + # Display only the name part + Write-Host ("{0}. {1}" -f ($i + 1), $savedPlans[$i].Split(' - ', 2)[1]) + } + Write-Host "N. Enter a new Plan ID" + + $choice = Read-Host -Prompt "Select an ID" + + if ($choice -eq 'N' -or $choice -eq 'n') { + # Flag to enter a new Plan ID + $planId = $null + } + elseif ($choice -match "^\d+$" -and [int]$choice -ge 1 -and [int]$choice -le $savedPlans.Count) { + $selectedIndex = [int]$choice - 1 + # Extract the ID part + $planId = $savedPlans[$selectedIndex].Split(' - ', 2)[0] + } + else { + Write-Host "Invalid selection. Exiting." + exit + } + } +} + +# If no plan was selected from the menu, or if config is empty/doesn't exist +if (-not $planId) { + $newPlanId = Read-Host -Prompt "Please enter the new Plan ID" + $planName = Read-Host -Prompt "Please enter a name for this plan (for your reference)" + + if (-not $newPlanId -or -not $planName) { + Write-Host "Plan ID and Plan Name cannot be empty. Exiting." + exit + } + + $newEntry = "$newPlanId - $planName" + + # Add the new entry to the config file + Add-Content -Path $configPath -Value $newEntry + Write-Host "New Plan ID saved: $newEntry" + $planId = $newPlanId +} + +Write-Host "Using Plan ID: $planId" + +# Initialize task data array +$taskData = @() + +# Retrieve and process tasks +Write-Host "Fetching tasks from plan..." +try { + try { + $tasks = Get-MgPlannerPlanTask -PlannerPlanId $planId -ErrorAction Stop + if (-not $tasks) { + Write-Error "No tasks found in the plan." + exit + } + + # Get all buckets in the plan to create a lookup table for bucket names + $buckets = Get-MgPlannerPlanBucket -PlannerPlanId $planId -ErrorAction Stop + $bucketNameLookup = @{} + foreach ($bucket in $buckets) { + $bucketNameLookup[$bucket.Id] = $bucket.Name + } + } + catch { + Write-Error "Failed to get tasks or buckets: $_" + exit 1 + } + + foreach ($task in $tasks) { + + $bucketName = $bucketNameLookup[$task.BucketId] + + Write-Host "Processing task: $($task.Title)" + + # Get task details for attachments and assigned users + try { + if (-not $task.Assignments) { + if ($selection -eq '1') { + $taskDetails = Get-MgPlannerTaskDetail -PlannerTaskId $task.Id + $attachments = "No references" + if ($taskDetails.References -and $taskDetails.References.AdditionalProperties) { + $foundUrls = @() + foreach ($key in $taskDetails.References.AdditionalProperties.Keys) { + $url = [System.Net.WebUtility]::UrlDecode($key) + $alias = $taskDetails.References.AdditionalProperties[$key].alias + + $urlIsGithub = $url -like "*github.com*" + $aliasIsGithub = $alias -like "*github.com*" + + if ($urlIsGithub -and $aliasIsGithub) { + if ($url -eq $alias) { + $foundUrls += $url + } + else { + $foundUrls += "$alias ($url)" + } + } + elseif ($urlIsGithub) { + $foundUrls += $url + } + elseif ($aliasIsGithub) { + $foundUrls += $alias + } + } + if ($foundUrls.Count -gt 0) { + $attachments = $foundUrls -join "; " + } + else { + $attachments = "No GitHub links" + } + } + + $taskData += [PSCustomObject]@{ + Name = "Unassigned" + Role = "" + Task = $task.Title + Bucket = $bucketNameLookup[$task.BucketId] + Attachments = $attachments + } + } + continue + } + + $taskDetails = Get-MgPlannerTaskDetail -PlannerTaskId $task.Id + + # Check for GitHub references + $attachments = "No references" + if ($taskDetails.References -and $taskDetails.References.AdditionalProperties) { + $foundUrls = @() + foreach ($key in $taskDetails.References.AdditionalProperties.Keys) { + $url = [System.Net.WebUtility]::UrlDecode($key) + $alias = $taskDetails.References.AdditionalProperties[$key].alias + + $urlIsGithub = $url -like "*github.com*" + $aliasIsGithub = $alias -like "*github.com*" + + if ($urlIsGithub -and $aliasIsGithub) { + if ($url -eq $alias) { + $foundUrls += $url + } + else { + $foundUrls += "$alias ($url)" + } + } + elseif ($urlIsGithub) { + $foundUrls += $url + } + elseif ($aliasIsGithub) { + $foundUrls += $alias + } + } + if ($foundUrls.Count -gt 0) { + $attachments = $foundUrls -join "; " + } + else { + $attachments = "No GitHub links" + } + } + else { + Write-Host "No references found in task details" + } + + # Get assigned users + $assignmentKeys = $task.Assignments.AdditionalProperties.Keys + $assignments = $task.Assignments.AdditionalProperties + + if ($assignmentKeys.Count -gt 0) { + # Sort keys by assignedDateTime + $sortedKeys = $assignmentKeys | Sort-Object { [datetime]$assignments[$_].assignedDateTime } + + $mainContributorKey = $sortedKeys[0] + $reviewerKey = if ($sortedKeys.Count -gt 1) { $sortedKeys[-1] } else { $null } + + foreach ($userId in $assignmentKeys) { + $assignment = $assignments[$userId] + $assignedDateTime = [datetime]$assignment.assignedDateTime + + if ($selection -eq '3' -and ($assignedDateTime -lt $startDate -or $assignedDateTime -ge $endDate)) { + continue + } + + $role = if ($userId -eq $mainContributorKey) { "Main Contributor" } elseif ($userId -eq $reviewerKey) { "Reviewer" } else { "" } + + try { + $user = Get-MgUser -UserId $userId -ErrorAction Stop + $userName = $user.DisplayName + } + catch { + Write-Host "Error getting user details: $_" + $userName = "$userId (Unable to get name)" + } + + # Add task to collection with individual user + $taskData += [PSCustomObject]@{ + Name = $userName + Role = $role + Task = $task.Title + Bucket = $bucketName + Attachments = $attachments + } + } + } + } + catch { + Write-Warning "Failed to process task: $($task.Title). Error: $_" + # Add error entry + $taskData += [PSCustomObject]@{ + Name = "Error Processing" + Role = "" + Task = $task.Title + Bucket = $bucketName + Attachments = "Error retrieving attachments" + } + } + } + + # Sort the data by Name + $taskData = $taskData | Sort-Object Name + + # Display preview of the data + Write-Host "`nPreview of exported data:" + $taskData | Format-Table -AutoSize + + # Get output filename from user + $outputFile = Read-Host -Prompt "Enter the desired name for the CSV file" + if (-not ($outputFile.EndsWith(".csv"))) { + $outputFile = "$outputFile.csv" + } + + # Export to CSV + $taskData | Export-Csv -Path $outputFile -NoTypeInformation -Force + Write-Host "Tasks exported successfully to $outputFile" + +} +catch { + Write-Error "Failed to fetch tasks. Error: $_" +} + +Write-Host "Script made by Ibitope Fatoki. Github ibi420" +Write-Host "Script completed." +Read-Host "Press Enter to exit" diff --git a/src/content/docs/report-scripts/Planner_Pull_Documentation.md b/src/content/docs/report-scripts/Planner_Pull_Documentation.md new file mode 100644 index 00000000..db55d25d --- /dev/null +++ b/src/content/docs/report-scripts/Planner_Pull_Documentation.md @@ -0,0 +1,97 @@ +# Planner Task Puller Script + +## Overview + +This PowerShell script connects to Microsoft Graph to retrieve tasks from a specified Microsoft Planner plan. It allows the user to pull all tasks, only those assigned to users, or tasks assigned to users within a specific date range. The script can store connection details for multiple plans in a `config.txt` file, allowing for easy switching between them. The script fetches task details, including bucket names, attachments (specifically looking for GitHub links), and assigned users with their roles (Main Contributor, Reviewer). The collected data is then exported to a user-specified CSV file. + +## Prerequisites + +* Windows operating system with PowerShell 7. You can grap powershell online at [Microsoft Page](https://learn.microsoft.com/en-gb/powershell/scripting/install/installing-powershell?view=powershell-7.5) or [Github Link](https://github.com/PowerShell/PowerShell?tab=readme-ov-file). +* An internet connection. +* A Microsoft 365 account with access to Microsoft Planner. +* The `Microsoft.Graph` PowerShell module. + +## Setup + +Before running the script for the first time, you need to install the `Microsoft.Graph` module. Open a PowerShell terminal and run the following command: + +```powershell +Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force +``` + +## Finding Your Plan ID + +The Plan ID is required to fetch tasks from a specific plan. You can find the Plan ID in the URL of your Planner board. + +1. Go to [Microsoft Planner](https://tasks.office.com/). +2. Open the plan you want to use. +3. Look at the URL in your browser's address bar. It will look something like this: + `https://planner.cloud.microsoft/webui/plan/PLANNER_ID/view/board?tid=BOARDID` +4. The `planId` is the alphanumeric string that follows `plan/`. Copy this value when the script prompts for it. + +### Video Tutorial + +For a visual guide on how to find the Plan ID, please watch this video (Covers Two methods): + +[Plan ID Tutorial](https://deakin365-my.sharepoint.com/:v:/g/personal/s223739207_deakin_edu_au/EeAm2dpPc3VGrh6DHzyHkOcBig0my4m3UYWG5HmGtFG09A?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=TVSwwN) + +## How to Run the Script + +[Script Demo](https://deakin365-my.sharepoint.com/:v:/g/personal/s223739207_deakin_edu_au/ETM6TddvX_9KhSbykjwiinMBgSIsZp8inzyoABN32SEFMg?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=7QDevO) + +1. Open a PowerShell terminal. +2. Navigate to the directory where the script is located. +3. Execute the script by running: `.\planner_pull.ps1` +4. Follow the on-screen prompts: + * **Choose an option:** Select whether to pull all tasks, only assigned ones, or assigned tasks within a date range. + * **Enter Date Range (if applicable):** If you choose to filter by date, you will be prompted to enter a start and end date in `YYYY-MM-DD` format. + * **Authenticate:** The script uses a device code flow for authentication. You will be prompted to open a URL in a web browser and enter a code to sign in to your Microsoft account. This is required before you can select a plan. + * **Choose a Plan:** + * If you have saved plans in `config.txt`, you will see a numbered list of them. Enter the number to select a plan. + * To add a new plan, choose the "Enter a new Plan ID" option (N). + * **Enter New Plan Details (if applicable):** + * Enter the new Plan ID. + * Enter a descriptive name for the plan. This name is for your reference and will be shown in the selection menu in the future. + * **Enter CSV Filename:** Provide a name for the output CSV file. + +## How it Works + +1. **Module Check:** The script first checks if the `Microsoft.Graph` module is installed. +2. **User Selection:** It prompts the user to choose between pulling all tasks, only assigned tasks, or assigned tasks within a date range. +3. **Date Range Input:** If the user chooses to filter by date, the script prompts for a start and end date. +4. **Authentication:** It connects to the Microsoft Graph API using a device code authentication flow. This is done early to ensure the script has the necessary permissions for subsequent steps. The script uses a minimal set of permissions required to read tasks and user profiles. +5. **Plan ID Configuration:** + * The script reads the `config.txt` file to find any saved plans. + * If saved plans are found, it displays them in a menu for the user to select. + * If the user opts to add a new plan, the script prompts for the new Plan ID and a descriptive name. + * The new plan entry is appended to `config.txt` in the format ` - `. This preserves existing entries. +6. **Data Retrieval:** + * It fetches all tasks for the selected Plan ID using `Get-MgPlannerPlanTask`. + * It fetches all buckets in the plan using `Get-MgPlannerPlanBucket` to create a lookup map of bucket IDs to bucket names. + * For each task, it retrieves detailed information using `Get-MgPlannerTaskDetail`. + * It retrieves user information for assigned users using `Get-MgUser`. +7. **Data Processing:** + * **Date Filtering:** If a date range is provided, the script filters out tasks that were not assigned within that range. + * **Bucket Name:** It matches the task's `bucketId` to the previously fetched list of buckets to get the bucket name. + * **Attachments:** It intelligently scans task references for GitHub links. It checks both the underlying URL and the display text (alias) for each reference. + * If a GitHub link is found in the URL, its display text is checked. If the display text is also a distinct GitHub link, both are preserved for context. Otherwise, only the clean URL is used. + * If a GitHub link is found only in the display text, the entire display text is captured. + This ensures that links are found even if entered incorrectly by users. + * **User Roles:** It determines user roles ("Main Contributor" or "Reviewer") based on the order of assignment. The first assigned user is considered the Main Contributor, and the last is the Reviewer. +8. **Output:** + * The script prompts the user for a desired output CSV filename. + * It compiles all the processed data. + * It displays a preview of the data in the console. + * It exports the final data to the specified CSV file. + +## Output CSV Columns + +* **Name:** The display name of the user assigned to the task. +* **Role:** The role of the user for that task (Main Contributor, Reviewer, or blank). +* **Task:** The title of the Planner task. +* **Bucket:** The name of the bucket the task belongs to. +* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. If a reference has a descriptive name that is also a distinct GitHub link, it will be formatted as `DisplayText (URL)`. Otherwise, only the clean URL is shown. This column may also contain a message indicating if no GitHub links were found. + +**Date of Creation:** 22/08/2025 +**Author:** Ibitope Fatoki + diff --git a/src/content/docs/report-scripts/config.txt b/src/content/docs/report-scripts/config.txt new file mode 100644 index 00000000..6a4c0a41 --- /dev/null +++ b/src/content/docs/report-scripts/config.txt @@ -0,0 +1,2 @@ +njykIFLDn0iAY1at7tACfcgADgBS - Ontrack Planner ID +mIelcQoIgkqhbM8WaPS3sMgAEmyV - Splashkit Plan ID diff --git a/src/content/docs/report-scripts/ontrack_tasks.csv b/src/content/docs/report-scripts/ontrack_tasks.csv new file mode 100644 index 00000000..81145db1 --- /dev/null +++ b/src/content/docs/report-scripts/ontrack_tasks.csv @@ -0,0 +1,114 @@ +"Name","Role","Task","Bucket","Attachments" +"ALEX BROWN","","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" +"ALEX BROWN","","Migrate groups.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/382" +"ALEX BROWN","","Fix change_remotes.sh","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-deploy/pull/32" +"ALEX BROWN","","Migrate student-task-list.coffee","Frontend Migration","No GitHub links" +"ALEX BROWN","Main Contributor","Migrate runtime.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/398" +"ALEX BROWN","","Migrate privacy-policy.coffee","Frontend Migration","No GitHub links" +"ALEX BROWN","Reviewer","Migrate outcome-service.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/390" +"ALEX BROWN","","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" +"CHELAKA YASODHANA PATHBERIYAGE","Reviewer","Add observer property to unit-level roles","Visitor Enhancement - NOT CAPSTONE","No GitHub links" +"CHELAKA YASODHANA PATHBERIYAGE","","Migrate portfolio-review-step.coffee","Frontend Migration","No GitHub links" +"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migrate group-selector.coffee","Frontend Migration","No GitHub links" +"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migration: task-ilo-alignment-rater","Frontend Migration","https://github.com/thoth-tech/documentation/pull/603; https://github.com/thoth-tech/doubtfire-web/pull/338" +"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" +"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","Main Contributor","Migrate recorder-service.coffee","Sprint 2 Doing","https://github.com/thoth-tech/doubtfire-web/pull/391" +"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migrate outcome-service.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/390" +"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migrate Group-member-list","Sprint 1 Doing","https://github.com/thoth-tech/doubtfire-web/pull/393" +"DUY NGUYEN","Main Contributor","Update unit model to host more details","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/71" +"DUY NGUYEN","","Enrich the unit card itself to show more info","CourseFlow","No GitHub links" +"DUY NGUYEN","Reviewer","Migrate runtime.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/398" +"DUY NGUYEN","Reviewer","Migrate groups.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/382" +"DUY NGUYEN","Main Contributor","Refactor CourseFlow code base for better components maintainence","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/385" +"DUY NGUYEN","","Add option to overload units to a teaching period","CourseFlow","No GitHub links" +"DUY NGUYEN","Main Contributor","Create test data for CourseFlow ( Course map, Course map units, Units)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/70" +"EDWARD NGUYEN","Main Contributor","Add summary of skills component","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/396" +"EDWARD NGUYEN","Reviewer","Update unit model to host more details","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/71" +"EDWARD NGUYEN","","Create a component to display credit points achieved.","CourseFlow","No GitHub links" +"EDWARD NGUYEN","","Populate the overlay component for the detailed unit with content of the unit - this needs to include unit requirements.","CourseFlow","https://github.com/thoth-tech/doubtfire-web/pull/215" +"EDWARD NGUYEN","Main Contributor","Add ability to mark unit as complete (phase 4)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/386 (https://github.com/thoth-tech/doubtfire-web/pull/396)" +"EDWARD NGUYEN","","Create test data for CourseFlow ( Course map, Course map units, Units)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/70" +"EDWARD NGUYEN","Reviewer","Remove alignment-bar-chart.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/395" +"EKAM BHULLAR","","Implement ngx graphs/charts in UI","Tutor Times","https://github.com/thoth-tech/doubtfire-web/pull/392" +"EKAM BHULLAR","Reviewer","Add summary of skills component","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/396" +"EKAM BHULLAR","Reviewer","Refactor CourseFlow code base for better components maintainence","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/385" +"EKAM BHULLAR","","Implement Real-Time Notification System","Tutor Times","No GitHub links" +"EKAM BHULLAR","","Migrate task-ilo-alignment-editor.coffee","Frontend Migration","No GitHub links" +"GAURAV MANOHAR MYANA","","Build Dashboard for admin","Tutor Times","No GitHub links" +"GAURAV MANOHAR MYANA","Reviewer","OnTrack Style Guide v0.1","Colour Vision Deficiency Accessibility","No GitHub links" +"HASINDU DESHITHA WELARATHNE","Main Contributor","Migrate students-list.coffee","Frontend Migration","https://github.com/thoth-tech/documentation/pull/597; https://github.com/thoth-tech/doubtfire-web/pull/389" +"HASINDU DESHITHA WELARATHNE","","Migrate recorder-service.coffee","Sprint 2 Doing","https://github.com/thoth-tech/doubtfire-web/pull/391" +"IBI FATOKI","","Create Script to Pull tasks from Planner","Visitor Enhancement - NOT CAPSTONE","https://github.com/thoth-tech/doubtfire-astro/pull/50" +"IBI FATOKI","Main Contributor","Security Issue: Publicly Accessible API Documentation via Swagger","OnTrack x AppAttack","https://github.com/thoth-tech/doubtfire-api/pull/73; https://github.com/thoth-tech/doubtfire-astro/pull/48/files" +"IRIS CHEN","","Web Security Audit: Security Misconfiguration","Security","No GitHub links" +"IRIS CHEN","Main Contributor","Standardize API Authentication Requirements","Security","https://github.com/thoth-tech/doubtfire-api/pull/72" +"IRIS CHEN","","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" +"IRIS CHEN","Main Contributor","Web Security Audit: Cryptographic Failures","Security","https://github.com/thoth-tech/doubtfire-astro/pull/45" +"IRIS CHEN","","Fix Insecure Direct Object References (IDOR) Vulnerability","Security","No GitHub links" +"IRIS CHEN","","Fix IDOR Vulnerability - User Data Access","Security","No GitHub links" +"JASON MARK VELLUCCI","","Migrate runtime.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/398" +"JASON MARK VELLUCCI","Main Contributor","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" +"JASON MARK VELLUCCI","","Migrate summary-task-status-scatter","Frontend Migration","No GitHub links" +"JASON MARK VELLUCCI","","Refactor CourseFlow code base for better components maintainence","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/385" +"JASON MARK VELLUCCI","Reviewer","Clean up src/app/admin/modals/create-unit-modal leftover files","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/388; https://github.com/thoth-tech/doubtfire-web/pull/350" +"JASON MARK VELLUCCI","Main Contributor","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" +"JASON MARK VELLUCCI","Reviewer","8.x and 9.x build documentation","Sprint 2 Code Review","No GitHub links" +"JASON MARK VELLUCCI","Reviewer","Migrate Group-member-list","Sprint 1 Doing","https://github.com/thoth-tech/doubtfire-web/pull/393" +"JASON MARK VELLUCCI","","Update unit model to host more details","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/71" +"JOSEPH KALAYATHANKAL SAJI","","Backend - Implement API Endpoints for SessionActivies and MarkingSession for Analytics","Sprint 2 Doing","No GitHub links" +"JOSEPH KALAYATHANKAL SAJI","Main Contributor","Backend - Create marking_sessions table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/4" +"JOSEPH KALAYATHANKAL SAJI","Main Contributor","OnTrack Style Guide v0.1","Colour Vision Deficiency Accessibility","No GitHub links" +"JOSEPH KALAYATHANKAL SAJI","Reviewer","Backend - Create session_activities table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/3" +"JOSH BEVAN","Reviewer","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" +"JOSH BEVAN","Reviewer","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" +"JOSH TALEV","Main Contributor","Clean up src/app/admin/modals/create-unit-modal leftover files","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/388; https://github.com/thoth-tech/doubtfire-web/pull/350" +"JOSH TALEV","Main Contributor","Migrate confirmation-modal.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/381" +"JOSH TALEV","Main Contributor","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" +"JOSH TALEV","Main Contributor","Backend - Create session_activities table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/3" +"JOSH TALEV","","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" +"LACHLAN MACKIE ROBINSON","Main Contributor","8.x and 9.x build documentation","Sprint 2 Code Review","No GitHub links" +"LACHLAN MACKIE ROBINSON","Main Contributor","Migrate outcome-service.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/390" +"LACHLAN MACKIE ROBINSON","","Migrate utilService.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/383" +"LACHLAN MACKIE ROBINSON","","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" +"LACHLAN MACKIE ROBINSON","Main Contributor","Remove alignment-bar-chart.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/395" +"LAMIA TABASSUM","","Add observer property to unit-level roles","Visitor Enhancement - NOT CAPSTONE","No GitHub links" +"MARTIN JOHN DOLORES","","Migrate confirmation-modal.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/381" +"MARTIN JOHN DOLORES","Main Contributor","Migrate utilService.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/383" +"MARTIN JOHN DOLORES","","Backend - Create new SessionTracker Service","Sprint 2 Doing","No GitHub links" +"MARTIN JOHN DOLORES","Reviewer","Backend - Create marking_sessions table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/4" +"MARTIN JOHN DOLORES","","Backend - Integrate SessionTracker service across related entities","Tutor Times","No GitHub links" +"MARTIN JOHN DOLORES","Main Contributor","Migrate groups.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/382" +"MARTIN JOHN DOLORES","","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" +"MARTIN JOHN DOLORES","Reviewer","Create test data for CourseFlow ( Course map, Course map units, Units)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/70" +"MARTIN JOHN DOLORES","","Web Security Audit: Cryptographic Failures","Security","https://github.com/thoth-tech/doubtfire-astro/pull/45" +"MARTIN JOHN DOLORES","Reviewer","Standardize API Authentication Requirements","Security","https://github.com/thoth-tech/doubtfire-api/pull/72" +"MARTIN JOHN DOLORES","","Backend - Create session_activities table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/3" +"MARTIN JOHN DOLORES","Reviewer","Security Issue: Publicly Accessible API Documentation via Swagger","OnTrack x AppAttack","https://github.com/thoth-tech/doubtfire-api/pull/73; https://github.com/thoth-tech/doubtfire-astro/pull/48/files" +"MILLICENT ACHIENG AMOLO","","Fix Frontend Admin Route Access Controls","Security","https://github.com/thoth-tech/doubtfire-astro/pull/49#issue-3340358209" +"PARTH SANJAYKUMAR VAGHELA","","Migrate unit-dates-selector.coffee","Sprint 2 Doing","No GitHub links" +"PASINDU FERNANDO","","Migrate students-list.coffee","Frontend Migration","https://github.com/thoth-tech/documentation/pull/597; https://github.com/thoth-tech/doubtfire-web/pull/389" +"PASINDU FERNANDO","Main Contributor","Migrate Group-member-list","Sprint 1 Doing","https://github.com/thoth-tech/doubtfire-web/pull/393" +"PASINDU FERNANDO","Reviewer","Migrate recorder-service.coffee","Sprint 2 Doing","https://github.com/thoth-tech/doubtfire-web/pull/391" +"PASINDU FERNANDO","Reviewer","Migrate confirmation-modal.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/381" +"PASINDU FERNANDO","Reviewer","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" +"RASHI AGRAWAL","Reviewer","Frontend: Add new SGE page","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/394" +"RASHI AGRAWAL","","Test backend and frontend integration","Sprint 1 Complete","No GitHub links" +"RASHI AGRAWAL","","Migrate unit-ilo-edit-modal.coffee","Frontend Migration","No GitHub links" +"SAHIRU HESHAN WITHANAGE","","Remove alignment-bar-chart.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/395" +"SAHIRU HESHAN WITHANAGE","","Add summary of skills component","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/396" +"SAHIRU HESHAN WITHANAGE","Reviewer","Add ability to mark unit as complete (phase 4)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/386 (https://github.com/thoth-tech/doubtfire-web/pull/396)" +"SAHIRU HESHAN WITHANAGE","","Frontend Work: Student search and selection","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/359" +"SAHIRU HESHAN WITHANAGE","Main Contributor","Frontend: Add new SGE page","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/394" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Frontend: Add new SGE page","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/394" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Standardize API Authentication Requirements","Security","https://github.com/thoth-tech/doubtfire-api/pull/72" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Add ability to mark unit as complete (phase 4)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/386 (https://github.com/thoth-tech/doubtfire-web/pull/396)" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Backend- In-system notification system","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/69" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Update documentation in doubtfire-astro for staff grant extension new APIs and FE components","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-astro/pull/40" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Frontend Work: Set up notification UI","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/353" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","Reviewer","Migrate utilService.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/383" +"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","Reviewer","Web Security Audit: Cryptographic Failures","Security","https://github.com/thoth-tech/doubtfire-astro/pull/45" +"TRIET LAM","","Migrate task-dashboard.coffee","Frontend Migration","No GitHub links" +"TRIET LAM","","Cleanup feedback.coffee","Frontend Migration","https://github.com/thoth-tech/doubtfire-web/pull/399" +"WAEL ALAHMADI","Main Contributor","Add observer property to unit-level roles","Visitor Enhancement - NOT CAPSTONE","No GitHub links" +"WAEL ALAHMADI","Reviewer","Migrate students-list.coffee","Frontend Migration","https://github.com/thoth-tech/documentation/pull/597; https://github.com/thoth-tech/doubtfire-web/pull/389" +"WAEL ALAHMADI","","Develop Tutor Time Dashboard ","Tutor Times","No GitHub links" From aaceab1658419b7fab414f75517328a0324eb320 Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Sat, 23 Aug 2025 19:16:45 +1000 Subject: [PATCH 02/10] Script refactoring --- .../docs/report-scripts/Planner_Pull.ps1 | 819 +++++++++++++----- .../Planner_Pull_Documentation.md | 116 +-- src/content/docs/report-scripts/config.json | 10 + src/content/docs/report-scripts/config.txt | 2 - .../docs/report-scripts/logs/script.log | 57 ++ .../docs/report-scripts/ontrack_tasks.csv | 114 --- 6 files changed, 751 insertions(+), 367 deletions(-) create mode 100644 src/content/docs/report-scripts/config.json delete mode 100644 src/content/docs/report-scripts/config.txt create mode 100644 src/content/docs/report-scripts/logs/script.log delete mode 100644 src/content/docs/report-scripts/ontrack_tasks.csv diff --git a/src/content/docs/report-scripts/Planner_Pull.ps1 b/src/content/docs/report-scripts/Planner_Pull.ps1 index 4cfb9d64..f24ce06b 100644 --- a/src/content/docs/report-scripts/Planner_Pull.ps1 +++ b/src/content/docs/report-scripts/Planner_Pull.ps1 @@ -1,241 +1,518 @@ -# Check for Microsoft.Graph module -if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { - Write-Host "Microsoft.Graph module is not installed. Please install it by running: Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force" - exit +# Script: Planner_pull.ps1 +# Description: Pulls tasks from a Microsoft Planner plan and exports them to a CSV, JSON, or Markdown file. +# Version: 3.3 +# Author: Ibitope Fatoki + +[CmdletBinding()] +param ( + [string]$ConfigPath = ".\config.json" +) + +# --- Script-level configuration --- +# Set logging level. Options: DEBUG, INFO, WARN, ERROR +$Script:LogLevel = 'INFO' + +# --- Function Definitions --- + +function Write-Log { + param( + [string]$Message, + [string]$Level = "INFO", + [string]$LogPath = ".\logs\script.log" + ) + + $logLevels = @{ + 'DEBUG' = 1 + 'INFO' = 2 + 'WARN' = 3 + 'ERROR' = 4 + } + + if ($logLevels[$Level.ToUpper()] -ge $logLevels[$Script:LogLevel.ToUpper()]) { + try { + $logDirectory = Split-Path -Path $LogPath -Parent + if (-not (Test-Path -Path $logDirectory -PathType Container)) { + New-Item -Path $logDirectory -ItemType Directory -Force -ErrorAction Stop | Out-Null + } + $logEntry = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [$($Level.ToUpper())] $Message" + Add-Content -Path $LogPath -Value $logEntry + } + catch { + Write-Warning "Could not write to log file at '$LogPath'. Error: $_" + } + } } -# --- Menu Selection --- -Write-Host "How would you like to pull the tasks? Select the relevant Option" -Write-Host "1. Pull all tasks (including unassigned)" -Write-Host "2. Pull only tasks with assigned users ONLY" -Write-Host "3. Pull tasks of assigned users based on a date range(great for progress and handover Doc)" -$selection = Read-Host -Prompt "Enter your choice (1, 2 or 3)" +function Select-TableColumns { + param($Data) + Write-Log -Level DEBUG -Message "Entering Select-TableColumns function." -if ($selection -ne '1' -and $selection -ne '2' -and $selection -ne '3') { - Write-Host "Invalid selection. Exiting." - exit + if (-not $Data) { + Write-Log -Level WARN -Message "Select-TableColumns called with no data." + return $null + } + + $availableColumns = $Data[0].PSObject.Properties.Name + Write-Log -Level DEBUG -Message "Available columns for selection: $($availableColumns -join ', ')" + Write-Host "`nSelect the columns to include in the Markdown report:" + for ($i = 0; $i -lt $availableColumns.Count; $i++) { + Write-Host ("{0}. {1}" -f ($i + 1), $availableColumns[$i]) + } + + $choice = Read-Host -Prompt "Enter the column numbers, separated by commas (e.g., 1,3,4), or press Enter for all" + Write-Log -Level INFO -Message "User column selection: '$choice'" + + if ([string]::IsNullOrWhiteSpace($choice)) { + Write-Log -Level DEBUG -Message "User selected all columns." + return $availableColumns + } + + $selectedColumns = @() + $choices = $choice -split ',' | ForEach-Object { $_.Trim() } + + foreach ($c in $choices) { + if ($c -match "^\d+$" -and [int]$c -ge 1 -and [int]$c -le $availableColumns.Count) { + $selectedIndex = [int]$c - 1 + if ($selectedColumns -notcontains $availableColumns[$selectedIndex]) { + $selectedColumns += $availableColumns[$selectedIndex] + } + } else { + Write-Log -Level WARN -Message "Invalid column selection: '$c'. Aborting export." + Write-Host "Invalid selection: '$c'. Aborting export." + return $null + } + } + + Write-Log -Level DEBUG -Message "Returning selected columns: $($selectedColumns -join ', ')" + return $selectedColumns +} + +function ConvertTo-MarkdownTable { + param ($Data) + Write-Log -Level DEBUG -Message "Entering ConvertTo-MarkdownTable function." + + if (-not $Data) { + Write-Log -Level WARN -Message "ConvertTo-MarkdownTable called with no data." + return "" + } + + $headers = $Data[0].PSObject.Properties.Name + $headerLine = "| $($headers -join ' | ') |" + $separatorArray = $headers | ForEach-Object { '-' * $_.Length } + $separatorLine = "| $($separatorArray -join ' | ') |" + + $dataRows = $Data | ForEach-Object { + $row = $_ + $values = $headers | ForEach-Object { + $value = $row.$_ + if ($null -eq $value) { + $value = "" + } + $sanitizedValue = $value.ToString() -replace '(\r|\n)', ' ' -replace '\|', '|' + $sanitizedValue + } + "| $($values -join ' | ') |" + } + + # Flatten the array of lines before joining to prevent nested array issues. + $allLines = @($headerLine, $separatorLine) + $dataRows + $markdown = $allLines -join [System.Environment]::NewLine + + Write-Log -Level DEBUG -Message "Exiting ConvertTo-MarkdownTable function. Markdown length: $($markdown.Length)" + return $markdown } -if ($selection -eq '3') { - $startDateStr = Read-Host -Prompt "Enter the start date (YYYY-MM-DD)" - $endDateStr = Read-Host -Prompt "Enter the end date (YYYY-MM-DD)" +function Connect-ToGraph { + Write-Log -Level DEBUG -Message "Entering Connect-ToGraph function." + Write-Host "Importing Required Microsoft.Graph sub modules..." + try { + Import-Module Microsoft.Graph.Planner -ErrorAction Stop + Import-Module Microsoft.Graph.Users -ErrorAction Stop + } + catch { + Write-Log -Level "ERROR" -Message "Failed to import required modules: $_" + Write-Error "Failed to import required modules. Please ensure Microsoft.Graph is installed correctly." + exit 1 + } + Write-Host "Authenticating to Microsoft Graph..." try { - $startDate = [datetime]::ParseExact($startDateStr, 'yyyy-MM-dd', $null) - $endDate = [datetime]::ParseExact($endDateStr, 'yyyy-MM-dd', $null).AddDays(1) # Adds one day to include the entire end day + $context = Get-MgContext + if ($context) { + Write-Log -Level INFO -Message "Already authenticated as $($context.Account)" + Write-Host "Already authenticated as $($context.Account)" + return $true + } + Connect-MgGraph -Scopes @("Tasks.Read", "Tasks.ReadWrite", "User.ReadBasic.All") -UseDeviceCode -Audience "organizations" + $context = Get-MgContext + if (-not $context) { + throw "Failed to establish connection" + } + Write-Host "Authentication successful as $($context.Account)" + Write-Log -Level INFO -Message "Authentication successful as $($context.Account)" + return $true } catch { - Write-Host "Invalid date format. Please use YYYY-MM-DD. Exiting." - exit + Write-Log -Level "ERROR" -Message "Authentication failed: $_" + Write-Error "Authentication failed: $_" + exit 1 + } +} + +function Get-Configurations { + param ([string]$Path) + Write-Log -Level DEBUG -Message "Entering Get-Configurations function for path: $Path" + if (Test-Path $Path) { + try { + $content = Get-Content -Path $Path -Raw + if ([string]::IsNullOrWhiteSpace($content)) { + Write-Log -Level INFO -Message "Config file is empty. Returning empty array." + return @() + } + $data = $content | ConvertFrom-Json + Write-Log -Level DEBUG -Message "Successfully read and parsed config file." + return @($data) + } + catch { + Write-Log -Level "ERROR" -Message "Failed to read or parse config file '$Path': $_" + Write-Error "Could not read or parse config file '$Path'. It might be corrupted." + return @() + } } + Write-Log -Level INFO -Message "Config file not found at '$Path'. Returning empty array." + return @() } -# Import required sub modules -Write-Host "Importing Required Microsoft.Graph sub modules..." -Import-Module Microsoft.Graph.Planner -Import-Module Microsoft.Graph.Users - -# Authenticate to Microsoft Graph -Write-Host "Authenticating to Microsoft Graph..." -try { - # Connect to Microsoft Graph with required permissions - Connect-MgGraph -Scopes @( - "Tasks.Read", - "Tasks.ReadWrite", - "User.ReadBasic.All" - ) -UseDeviceCode -Audience "organizations" - - # Verify connection - $context = Get-MgContext - if (-not $context) { - throw "Failed to establish connection" - } - Write-Host "Authentication successful as $($context.Account)" +function Save-Configurations { + param ([string]$Path, $ConfigData) + Write-Log -Level DEBUG -Message "Entering Save-Configurations function for path: $Path" + try { + $jsonOutput = if ($ConfigData -is [array] -and $ConfigData.Count -eq 1) { + Write-Log -Level DEBUG -Message "Saving single-item array with comma-prefix for backwards compatibility." + ,$ConfigData | ConvertTo-Json -Depth 5 + } else { + Write-Log -Level DEBUG -Message "Saving multi-item array or empty array." + $ConfigData | ConvertTo-Json -Depth 5 + } + $jsonOutput | Set-Content -Path $Path + Write-Log -Level INFO -Message "Successfully saved configuration to '$Path'." + } + catch { + $errorMessage = $_.Exception.Message + Write-Log -Level "ERROR" -Message "Failed to save config file '$Path': $errorMessage" + Write-Error "Failed to save configuration to '$Path'. Reason: $errorMessage" + } } -catch { - Write-Error "Authentication failed: $_" - exit 1 + +function Manage-Configurations { + param ([string]$ConfigPath) + Write-Log -Level INFO -Message "Entering plan management utility." + + while ($true) { + [array]$savedPlans = Get-Configurations -Path $ConfigPath + + Write-Host "`n--- Manage Saved Plans ---" + if ($savedPlans.Count -eq 0) { + Write-Host "No saved plans found." + } else { + for ($i = 0; $i -lt $savedPlans.Count; $i++) { + Write-Host ("{0}. {1} ({2})" -f ($i + 1), $savedPlans[$i].name, $savedPlans[$i].planId) + } + } + + Write-Host "--------------------------" + Write-Host "A. Add a new plan" + Write-Host "D. Delete a plan" + Write-Host "Q. Quit to main menu" + + $choice = Read-Host -Prompt "Enter your choice" + Write-Log -Level INFO -Message "User selected management option: '$choice'" + + switch ($choice.ToUpper()) { + 'A' { + $newPlanId = Read-Host -Prompt "Please enter the new Plan ID" + $planName = Read-Host -Prompt "Please enter a name for this plan" + + if (-not $newPlanId -or -not $planName) { + Write-Log -Level WARN -Message "User tried to add a plan with empty ID or name." + Write-Host "Plan ID and Plan Name cannot be empty." + continue + } + Write-Log -Level INFO -Message "Adding new plan: Name='$planName', ID='$newPlanId'" + $newPlan = @{ planId = $newPlanId; name = $planName } + $savedPlans += $newPlan + Save-Configurations -Path $ConfigPath -ConfigData $savedPlans + Write-Host "Plan '$planName' added successfully." + } + 'D' { + if ($savedPlans.Count -eq 0) { + Write-Host "No plans to delete." + continue + } + + Write-Host "`nWhich plan would you like to delete?" + for ($i = 0; $i -lt $savedPlans.Count; $i++) { + Write-Host ("{0}. {1}" -f ($i + 1), $savedPlans[$i].name) + } + + $delChoice = Read-Host -Prompt "Enter the number of the plan to delete" + if ($delChoice -match "^\d+$" -and [int]$delChoice -ge 1 -and [int]$delChoice -le $savedPlans.Count) { + $selectedIndex = [int]$delChoice - 1 + $planNameToDelete = $savedPlans[$selectedIndex].name + Write-Log -Level INFO -Message "User chose to delete plan #$delChoice ('$planNameToDelete')." + $confirmation = Read-Host "Are you sure you want to delete '$planNameToDelete'? (y/n)" + if ($confirmation.ToUpper() -eq 'Y') { + Write-Log -Level INFO -Message "User confirmed deletion." + $savedPlans = $savedPlans | Where-Object { $_.planId -ne $savedPlans[$selectedIndex].planId } + Save-Configurations -Path $ConfigPath -ConfigData $savedPlans + Write-Host "'$planNameToDelete' has been deleted." + } else { + Write-Log -Level INFO -Message "User cancelled deletion." + } + } else { + Write-Log -Level WARN -Message "User entered invalid number for deletion: '$delChoice'" + Write-Host "Invalid number." + } + } + 'Q' { + Write-Log -Level INFO -Message "Exiting plan management utility." + return + } + default { + Write-Log -Level WARN -Message "User entered invalid management option: '$choice'" + Write-Host "Invalid option." + } + } + } } -# --- Plan ID Configuration --- -$configPath = ".\config.txt" -$planId = $null +function Select-PlannerPlan { + param ([string]$ConfigPath) + Write-Log -Level DEBUG -Message "Entering Select-PlannerPlan function." + [array]$savedPlans = Get-Configurations -Path $ConfigPath + $planId = $null -if (Test-Path $configPath) { - $savedPlans = @(Get-Content $configPath | Where-Object { $_ -match ".+ - .+" }) # Filter for valid entries - if ($savedPlans) { - Write-Host "Please choose a saved Plan ID or enter a new one:" + if ($savedPlans.Count -gt 0) { + Write-Host "Please choose a saved Plan or enter a new one:" for ($i = 0; $i -lt $savedPlans.Count; $i++) { - # Display only the name part - Write-Host ("{0}. {1}" -f ($i + 1), $savedPlans[$i].Split(' - ', 2)[1]) + Write-Host ("{0}. {1}" -f ($i + 1), $savedPlans[$i].name) } - Write-Host "N. Enter a new Plan ID" + Write-Host "N. Enter a new Plan" - $choice = Read-Host -Prompt "Select an ID" + $choice = Read-Host -Prompt "Select a plan" + Write-Log -Level INFO -Message "User plan selection: '$choice'" if ($choice -eq 'N' -or $choice -eq 'n') { - # Flag to enter a new Plan ID $planId = $null } elseif ($choice -match "^\d+$" -and [int]$choice -ge 1 -and [int]$choice -le $savedPlans.Count) { $selectedIndex = [int]$choice - 1 - # Extract the ID part - $planId = $savedPlans[$selectedIndex].Split(' - ', 2)[0] + $planId = $savedPlans[$selectedIndex].planId } else { + Write-Log -Level WARN -Message "Invalid plan selection. Exiting." Write-Host "Invalid selection. Exiting." exit } } + + if (-not $planId) { + Write-Log -Level INFO -Message "User chose to enter a new plan." + $newPlanId = Read-Host -Prompt "Please enter the new Plan ID" + $planName = Read-Host -Prompt "Please enter a name for this plan (for your reference)" + + if (-not $newPlanId -or -not $planName) { + Write-Log -Level WARN -Message "New plan details were empty. Exiting." + Write-Host "Plan ID and Plan Name cannot be empty. Exiting." + exit + } + + $newPlan = @{ planId = $newPlanId; name = $planName } + $savedPlans += $newPlan + Save-Configurations -Path $ConfigPath -ConfigData $savedPlans + Write-Host "New Plan saved: $planName" + $planId = $newPlanId + } + + Write-Log -Level INFO -Message "Using Plan ID: $planId" + Write-Host "Using Plan ID: $planId" + return $planId } -# If no plan was selected from the menu, or if config is empty/doesn't exist -if (-not $planId) { - $newPlanId = Read-Host -Prompt "Please enter the new Plan ID" - $planName = Read-Host -Prompt "Please enter a name for this plan (for your reference)" +function Select-PlannerBucket { + param ([string]$PlanId) + Write-Log -Level DEBUG -Message "Entering Select-PlannerBucket for Plan ID: $PlanId" + Write-Host "Fetching buckets for plan..." + try { + $buckets = Get-MgPlannerPlanBucket -PlannerPlanId $PlanId -ErrorAction Stop + } + catch { + Write-Log -Level "ERROR" -Message "Failed to get buckets for plan '$PlanId': $_" + Write-Error "Failed to get buckets for plan '$PlanId': $_" + return $null + } - if (-not $newPlanId -or -not $planName) { - Write-Host "Plan ID and Plan Name cannot be empty. Exiting." - exit + if ($buckets.Count -eq 0) { + Write-Log -Level WARN -Message "No buckets found in plan '$PlanId'." + Write-Host "No buckets found in this plan." + return $null } - $newEntry = "$newPlanId - $planName" + Write-Host "Please choose a bucket:" + for ($i = 0; $i -lt $buckets.Count; $i++) { + Write-Host ("{0}. {1}" -f ($i + 1), $buckets[$i].Name) + } - # Add the new entry to the config file - Add-Content -Path $configPath -Value $newEntry - Write-Host "New Plan ID saved: $newEntry" - $planId = $newPlanId + $choice = Read-Host -Prompt "Select a bucket" + if ($choice -match "^\d+$" -and [int]$choice -ge 1 -and [int]$choice -le $buckets.Count) { + $selectedIndex = [int]$choice - 1 + $selectedBucket = $buckets[$selectedIndex] + Write-Log -Level INFO -Message "User selected bucket: Name='$($selectedBucket.Name)', ID='$($selectedBucket.Id)'" + return $selectedBucket.Id + } + else { + Write-Log -Level WARN -Message "User entered invalid bucket selection: '$choice'" + Write-Host "Invalid selection. No bucket filter will be applied." + return $null + } } -Write-Host "Using Plan ID: $planId" +function Select-TaskStatus { + Write-Log -Level DEBUG -Message "Entering Select-TaskStatus function." + Write-Host "Filter by task status:" + Write-Host "1. Not Started" + Write-Host "2. In Progress" + Write-Host "3. Completed" + + $choice = Read-Host -Prompt "Enter your choice" + Write-Log -Level INFO -Message "User status filter selection: '$choice'" + switch ($choice) { + '1' { return "Not Started" } + '2' { return "In Progress" } + '3' { return "Completed" } + default { + Write-Log -Level WARN -Message "Invalid status selection." + Write-Host "Invalid selection. No status filter will be applied." + return $null + } + } +} + +function Get-TaskReferences { + param ($TaskDetails) + Write-Log -Level DEBUG -Message "Entering Get-TaskReferences function." + + if (-not $TaskDetails.References -or -not $TaskDetails.References.AdditionalProperties) { + return "No references" + } + + $foundUrls = @() + foreach ($key in $TaskDetails.References.AdditionalProperties.Keys) { + $url = [System.Net.WebUtility]::UrlDecode($key) + $alias = $TaskDetails.References.AdditionalProperties[$key].alias -# Initialize task data array -$taskData = @() + $urlIsGithub = $url -like "*github.com*" + $aliasIsGithub = $alias -like "*github.com*" -# Retrieve and process tasks -Write-Host "Fetching tasks from plan..." -try { + if ($urlIsGithub -and $aliasIsGithub) { + if ($url -eq $alias) { $foundUrls += $url } else { $foundUrls += "$alias ($url)" } + } elseif ($urlIsGithub) { + $foundUrls += $url + } elseif ($aliasIsGithub) { + $foundUrls += $alias + } + } + + if ($foundUrls.Count -gt 0) { + $result = $foundUrls -join "; " + Write-Log -Level DEBUG -Message "Found $($foundUrls.Count) GitHub links." + return $result + } else { + return "" # No GitHub links found + } +} + +function Get-PlannerTasks { + param ( + [string]$PlanId, + [string]$Selection, + [datetime]$StartDate, + [datetime]$EndDate, + [string]$BucketId, + [string]$Status + ) + Write-Log -Level INFO -Message "Starting Get-PlannerTasks for Plan ID '$PlanId'. Selection: $Selection, BucketId: $BucketId, Status: $Status" + + Write-Host "Fetching tasks from plan..." try { - $tasks = Get-MgPlannerPlanTask -PlannerPlanId $planId -ErrorAction Stop - if (-not $tasks) { - Write-Error "No tasks found in the plan." - exit + $allTasks = Get-MgPlannerPlanTask -PlannerPlanId $PlanId -ErrorAction Stop + if (-not $allTasks) { + Write-Log -Level WARN -Message "No tasks found in the plan." + Write-Warning "No tasks found in the plan." + return @() } + Write-Log -Level INFO -Message "Fetched $($allTasks.Count) total tasks from plan." - # Get all buckets in the plan to create a lookup table for bucket names - $buckets = Get-MgPlannerPlanBucket -PlannerPlanId $planId -ErrorAction Stop - $bucketNameLookup = @{} - foreach ($bucket in $buckets) { - $bucketNameLookup[$bucket.Id] = $bucket.Name + $tasksToProcess = $allTasks + if ($BucketId) { + $tasksToProcess = $tasksToProcess | Where-Object { $_.BucketId -eq $BucketId } + Write-Log -Level INFO -Message "Filtered by Bucket ID '$BucketId'. $($tasksToProcess.Count) tasks remain." + } + if ($Status) { + switch ($Status) { + "Not Started" { $tasksToProcess = $tasksToProcess | Where-Object { $_.PercentComplete -eq 0 } } + "In Progress" { $tasksToProcess = $tasksToProcess | Where-Object { $_.PercentComplete -gt 0 -and $_.PercentComplete -lt 100 } } + "Completed" { $tasksToProcess = $tasksToProcess | Where-Object { $_.PercentComplete -eq 100 } } + } + Write-Log -Level INFO -Message "Filtered by Status '$Status'. $($tasksToProcess.Count) tasks remain." + } + + if ($tasksToProcess.Count -eq 0) { + Write-Log -Level WARN -Message "No tasks match the specified filter criteria." + Write-Warning "No tasks match the specified filter criteria." + return @() } + + $buckets = Get-MgPlannerPlanBucket -PlannerPlanId $PlanId -ErrorAction Stop + $bucketNameLookup = @{} + foreach ($bucket in $buckets) { $bucketNameLookup[$bucket.Id] = $bucket.Name } + Write-Log -Level DEBUG -Message "Created lookup for $($buckets.Count) buckets." } catch { + Write-Log -Level "ERROR" -Message "Failed to get tasks or buckets for plan '$PlanId': $_" Write-Error "Failed to get tasks or buckets: $_" exit 1 } - foreach ($task in $tasks) { + $totalTasks = $tasksToProcess.Count + $processedCount = 0 - $bucketName = $bucketNameLookup[$task.BucketId] + $taskData = foreach ($task in $tasksToProcess) { + $processedCount++ + Write-Progress -Activity "Processing Tasks" -Status "Task $processedCount of $totalTasks" -PercentComplete (($processedCount / $totalTasks) * 100) + Write-Log -Level DEBUG -Message "Processing task: ID=$($task.Id), Title='$($task.Title)'" - Write-Host "Processing task: $($task.Title)" + $bucketName = $bucketNameLookup[$task.BucketId] - # Get task details for attachments and assigned users try { if (-not $task.Assignments) { if ($selection -eq '1') { $taskDetails = Get-MgPlannerTaskDetail -PlannerTaskId $task.Id - $attachments = "No references" - if ($taskDetails.References -and $taskDetails.References.AdditionalProperties) { - $foundUrls = @() - foreach ($key in $taskDetails.References.AdditionalProperties.Keys) { - $url = [System.Net.WebUtility]::UrlDecode($key) - $alias = $taskDetails.References.AdditionalProperties[$key].alias - - $urlIsGithub = $url -like "*github.com*" - $aliasIsGithub = $alias -like "*github.com*" - - if ($urlIsGithub -and $aliasIsGithub) { - if ($url -eq $alias) { - $foundUrls += $url - } - else { - $foundUrls += "$alias ($url)" - } - } - elseif ($urlIsGithub) { - $foundUrls += $url - } - elseif ($aliasIsGithub) { - $foundUrls += $alias - } - } - if ($foundUrls.Count -gt 0) { - $attachments = $foundUrls -join "; " - } - else { - $attachments = "No GitHub links" - } - } + $attachments = Get-TaskReferences -TaskDetails $taskDetails - $taskData += [PSCustomObject]@{ - Name = "Unassigned" - Role = "" - Task = $task.Title - Bucket = $bucketNameLookup[$task.BucketId] - Attachments = $attachments - } + [PSCustomObject]@{ Name = "Unassigned"; Role = ""; Task = $task.Title; Bucket = $bucketName; Attachments = $attachments; Status = switch ($task.PercentComplete) { 0 { "Not Started" } 100 { "Completed" } default { "In Progress" } } } } continue } $taskDetails = Get-MgPlannerTaskDetail -PlannerTaskId $task.Id + $attachments = Get-TaskReferences -TaskDetails $taskDetails - # Check for GitHub references - $attachments = "No references" - if ($taskDetails.References -and $taskDetails.References.AdditionalProperties) { - $foundUrls = @() - foreach ($key in $taskDetails.References.AdditionalProperties.Keys) { - $url = [System.Net.WebUtility]::UrlDecode($key) - $alias = $taskDetails.References.AdditionalProperties[$key].alias - - $urlIsGithub = $url -like "*github.com*" - $aliasIsGithub = $alias -like "*github.com*" - - if ($urlIsGithub -and $aliasIsGithub) { - if ($url -eq $alias) { - $foundUrls += $url - } - else { - $foundUrls += "$alias ($url)" - } - } - elseif ($urlIsGithub) { - $foundUrls += $url - } - elseif ($aliasIsGithub) { - $foundUrls += $alias - } - } - if ($foundUrls.Count -gt 0) { - $attachments = $foundUrls -join "; " - } - else { - $attachments = "No GitHub links" - } - } - else { - Write-Host "No references found in task details" - } - - # Get assigned users $assignmentKeys = $task.Assignments.AdditionalProperties.Keys $assignments = $task.Assignments.AdditionalProperties if ($assignmentKeys.Count -gt 0) { - # Sort keys by assignedDateTime $sortedKeys = $assignmentKeys | Sort-Object { [datetime]$assignments[$_].assignedDateTime } - $mainContributorKey = $sortedKeys[0] $reviewerKey = if ($sortedKeys.Count -gt 1) { $sortedKeys[-1] } else { $null } @@ -243,7 +520,8 @@ try { $assignment = $assignments[$userId] $assignedDateTime = [datetime]$assignment.assignedDateTime - if ($selection -eq '3' -and ($assignedDateTime -lt $startDate -or $assignedDateTime -ge $endDate)) { + if ($selection -eq '3' -and ($assignedDateTime -lt $StartDate -or $assignedDateTime -ge $EndDate)) { + Write-Log -Level DEBUG -Message "Skipping user '$userId' in task '$($task.Title)' due to date filter." continue } @@ -254,56 +532,193 @@ try { $userName = $user.DisplayName } catch { - Write-Host "Error getting user details: $_" + Write-Log -Level "WARN" -Message "Could not get user details for ID '$userId': $_" $userName = "$userId (Unable to get name)" } - # Add task to collection with individual user - $taskData += [PSCustomObject]@{ - Name = $userName - Role = $role - Task = $task.Title - Bucket = $bucketName - Attachments = $attachments - } + [PSCustomObject]@{ Name = $userName; Role = $role; Task = $task.Title; Bucket = $bucketName; Attachments = $attachments; Status = switch ($task.PercentComplete) { 0 { "Not Started" } 100 { "Completed" } default { "In Progress" } } } } } } catch { + Write-Log -Level "WARN" -Message "Failed to process task '$($task.Title)' (ID: $($task.Id)). Error: $_" Write-Warning "Failed to process task: $($task.Title). Error: $_" - # Add error entry - $taskData += [PSCustomObject]@{ - Name = "Error Processing" - Role = "" - Task = $task.Title - Bucket = $bucketName - Attachments = "Error retrieving attachments" - } + [PSCustomObject]@{ Name = "Error Processing"; Role = ""; Task = $task.Title; Bucket = $bucketName; Attachments = "Error retrieving details"; Status = "Unknown" } } } + Write-Progress -Activity "Processing Tasks" -Completed + Write-Log -Level INFO -Message "Finished processing tasks. Returning $($taskData.Count) records." + return $taskData +} - # Sort the data by Name - $taskData = $taskData | Sort-Object Name +function Export-Data { + param ($TaskData) + Write-Log -Level DEBUG -Message "Entering Export-Data function." + + if (-not $TaskData) { + Write-Log -Level WARN -Message "No data passed to Export-Data." + Write-Host "No data to export." + return + } + + $sortedData = $TaskData | Sort-Object Name - # Display preview of the data Write-Host "`nPreview of exported data:" - $taskData | Format-Table -AutoSize + $previewColumns = @('Name', 'Task', 'Bucket', 'Status') + $sortedData | Format-Table -Property $previewColumns -AutoSize + + $formatChoice = Read-Host -Prompt "Choose export format: (1) CSV, (2) JSON, or (3) Markdown" + Write-Log -Level INFO -Message "User chose export format: $formatChoice" + + # --- Column Selection for applicable formats --- + $reportData = $sortedData + if ($formatChoice -eq '1' -or $formatChoice -eq '3') { # CSV or Markdown + $formatName = if ($formatChoice -eq '1') { "CSV" } else { "Markdown" } + $selectedColumns = Select-TableColumns -Data $sortedData -FormatName $formatName + if (-not $selectedColumns) { + Write-Log -Level WARN -Message "User cancelled or failed column selection. Aborting export." + return + } + + if ($selectedColumns.Count -ne $sortedData[0].PSObject.Properties.Name.Count) { + $reportData = $sortedData | Select-Object -Property $selectedColumns + Write-Log -Level INFO -Message "User filtered columns for export: $($selectedColumns -join ', ')" + } + } - # Get output filename from user - $outputFile = Read-Host -Prompt "Enter the desired name for the CSV file" - if (-not ($outputFile.EndsWith(".csv"))) { - $outputFile = "$outputFile.csv" + # --- File Export Logic --- + switch ($formatChoice) { + '1' { # CSV + $outputFile = Read-Host -Prompt "Enter the desired name for the CSV file" + if (-not ($outputFile.EndsWith(".csv"))) { $outputFile = "$outputFile.csv" } + Write-Log -Level INFO -Message "Exporting to CSV at '$outputFile'" + try { + $reportData | Export-Csv -Path $outputFile -NoTypeInformation -Force + Write-Host "Tasks exported successfully to $outputFile" + } + catch { + Write-Log -Level "ERROR" -Message "Failed to export CSV to '$outputFile': $_" + Write-Error "Failed to export CSV file: $_" + } + } + '2' { # JSON + $outputFile = Read-Host -Prompt "Enter the desired name for the JSON file" + if (-not ($outputFile.EndsWith(".json"))) { $outputFile = "$outputFile.json" } + Write-Log -Level INFO -Message "Exporting to JSON at '$outputFile'" + try { + $reportData | ConvertTo-Json -Depth 10 | Set-Content -Path $outputFile + Write-Host "Tasks exported successfully to $outputFile" + } + catch { + Write-Log -Level "ERROR" -Message "Failed to export JSON to '$outputFile': $_" + Write-Error "Failed to export JSON file: $_" + } + } + '3' { # Markdown + $outputFile = Read-Host -Prompt "Enter the desired name for the MD file" + if (-not ($outputFile.EndsWith(".md"))) { $outputFile = "$outputFile.md" } + Write-Log -Level INFO -Message "Exporting to Markdown at '$outputFile'" + try { + $markdownTable = ConvertTo-MarkdownTable -Data $reportData + Set-Content -Path $outputFile -Value $markdownTable + Write-Host "Tasks exported successfully to $outputFile" + } + catch { + Write-Log -Level "ERROR" -Message "Failed to export Markdown to '$outputFile': $_" + Write-Error "Failed to export Markdown file: $_" + } + } + default { + Write-Log -Level WARN -Message "User entered invalid export format: $formatChoice" + Write-Host "Invalid format selection. Export cancelled." + } } +} + +# --- Main Script --- - # Export to CSV - $taskData | Export-Csv -Path $outputFile -NoTypeInformation -Force - Write-Host "Tasks exported successfully to $outputFile" +# --- Pre-flight Checks --- +# Check for PowerShell 7+ +if ($PSVersionTable.PSVersion.Major -lt 7) { + Write-Error "This script requires PowerShell 7 or higher. You are running version $($PSVersionTable.PSVersion). Please start the script using PowerShell 7 (pwsh.exe)." + if ($Host.Name -eq "ConsoleHost") { + Read-Host "Press Enter to exit" + } + exit 1 } -catch { - Write-Error "Failed to fetch tasks. Error: $_" + +# Check for module dependency +if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { + Write-Host "Microsoft.Graph module is not installed. Please install it by running: Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force" + exit +} + +Write-Log -Message "Script started." + +while ($true) { + Write-Host "`n--- Microsoft Planner Task Exporter ---" + Write-Host "1. Pull all tasks (including unassigned)" + Write-Host "2. Pull only tasks with assigned users" + Write-Host "3. Pull tasks of assigned users based on a date range" + Write-Host "4. Pull tasks from a specific bucket" + Write-Host "5. Pull tasks by completion status" + Write-Host "6. Manage saved plans" + Write-Host "Q. Quit" + $selection = Read-Host -Prompt "Enter your choice" + + if ($selection.ToUpper() -eq 'Q') { + break + } + + if ($selection -notin '1', '2', '3', '4', '5', '6') { + Write-Log -Level WARN -Message "User selected invalid main menu option: $selection" + Write-Host "Invalid selection." + continue + } + + Connect-ToGraph + + if ($selection -eq '6') { + Manage-Configurations -ConfigPath $ConfigPath + continue + } + + $planId = Select-PlannerPlan -ConfigPath $ConfigPath + if (-not $planId) { continue } + + $startDate = $null + $endDate = $null + $bucketId = $null + $status = $null + + if ($selection -eq '3') { + try { + $startDateStr = Read-Host -Prompt "Enter the start date (YYYY-MM-DD)" + $startDate = [datetime]::ParseExact($startDateStr, 'yyyy-MM-dd', $null) + $endDateStr = Read-Host -Prompt "Enter the end date (YYYY-MM-DD)" + $endDate = [datetime]::ParseExact($endDateStr, 'yyyy-MM-dd', $null).AddDays(1) + Write-Log -Level INFO -Message "Date filter selected. Start: $startDate, End: $endDate" + } + catch { + Write-Log -Level WARN -Message "User entered invalid date format." + Write-Host "Invalid date format. Please use YYYY-MM-DD." + continue + } + } + elseif ($selection -eq '4') { + $bucketId = Select-PlannerBucket -PlanId $planId + } + elseif ($selection -eq '5') { + $status = Select-TaskStatus + } + + $taskData = Get-PlannerTasks -PlanId $planId -Selection $selection -StartDate $startDate -EndDate $endDate -BucketId $bucketId -Status $status + Export-Data -TaskData $taskData + + Read-Host "Press Enter to return to the main menu" } -Write-Host "Script made by Ibitope Fatoki. Github ibi420" -Write-Host "Script completed." -Read-Host "Press Enter to exit" +Write-Log -Message "Script finished." +Write-Host "Script made by Ibitope Fatoki. Github ibi420." +Write-Host "Exiting." diff --git a/src/content/docs/report-scripts/Planner_Pull_Documentation.md b/src/content/docs/report-scripts/Planner_Pull_Documentation.md index db55d25d..26b0693a 100644 --- a/src/content/docs/report-scripts/Planner_Pull_Documentation.md +++ b/src/content/docs/report-scripts/Planner_Pull_Documentation.md @@ -1,19 +1,32 @@ -# Planner Task Puller Script +# Planner Task Puller Script Documentation ## Overview -This PowerShell script connects to Microsoft Graph to retrieve tasks from a specified Microsoft Planner plan. It allows the user to pull all tasks, only those assigned to users, or tasks assigned to users within a specific date range. The script can store connection details for multiple plans in a `config.txt` file, allowing for easy switching between them. The script fetches task details, including bucket names, attachments (specifically looking for GitHub links), and assigned users with their roles (Main Contributor, Reviewer). The collected data is then exported to a user-specified CSV file. +This PowerShell script connects to Microsoft Graph to retrieve tasks from a specified Microsoft Planner plan. It is a flexible, menu-driven tool that allows for powerful filtering and multiple export formats. + +The script can store connection details for multiple plans in a `config.json` file, allowing for easy switching between them. It fetches task details, including bucket names, completion status, attachments (specifically looking for GitHub links), and assigned users with their roles (Main Contributor, Reviewer). The collected data can then be exported to a **CSV**, **JSON**, or **Markdown** file. + +For CSV and Markdown exports, the script allows you to interactively select which data columns to include, making it ideal for generating custom reports. + +## Key Features + +* **Multiple Filtering Options:** Pull tasks by assignment, date range, completion status, or specific bucket. +* **Multiple Export Formats:** Export your data to CSV, JSON, or a report-ready Markdown table. +* **Customizable Columns:** For CSV and Markdown exports, you can choose exactly which columns to include. +* **Plan Management:** An interactive utility to add, delete, and view your saved Planner plans. +* **Robust Logging:** Creates a detailed log file in the `\logs` directory, with a configurable log level for easier debugging. +* **Environment Checks:** Automatically checks for the required PowerShell version (7+) and `Microsoft.Graph` module to prevent errors. ## Prerequisites -* Windows operating system with PowerShell 7. You can grap powershell online at [Microsoft Page](https://learn.microsoft.com/en-gb/powershell/scripting/install/installing-powershell?view=powershell-7.5) or [Github Link](https://github.com/PowerShell/PowerShell?tab=readme-ov-file). +* **PowerShell 7 or higher.** The script will not run on older versions like Windows PowerShell 5.1. You can get PowerShell 7 from the [Microsoft Page](https://learn.microsoft.com/en-gb/powershell/scripting/install/installing-powershell?view=powershell-7.5) or [GitHub](https://github.com/PowerShell/PowerShell). * An internet connection. * A Microsoft 365 account with access to Microsoft Planner. * The `Microsoft.Graph` PowerShell module. ## Setup -Before running the script for the first time, you need to install the `Microsoft.Graph` module. Open a PowerShell terminal and run the following command: +Before running the script for the first time, you need to install the `Microsoft.Graph` module. Open a PowerShell 7 terminal and run the following command: ```powershell Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force @@ -26,8 +39,8 @@ The Plan ID is required to fetch tasks from a specific plan. You can find the Pl 1. Go to [Microsoft Planner](https://tasks.office.com/). 2. Open the plan you want to use. 3. Look at the URL in your browser's address bar. It will look something like this: - `https://planner.cloud.microsoft/webui/plan/PLANNER_ID/view/board?tid=BOARDID` -4. The `planId` is the alphanumeric string that follows `plan/`. Copy this value when the script prompts for it. + `https://tasks.office.com/your-tenant.com/en-US/Home/Plan?planId=YOUR_PLAN_ID&ownerId=...` +4. The `planId` is the alphanumeric string that follows `planId=`. Copy this value. ### Video Tutorial @@ -39,59 +52,64 @@ For a visual guide on how to find the Plan ID, please watch this video (Covers T [Script Demo](https://deakin365-my.sharepoint.com/:v:/g/personal/s223739207_deakin_edu_au/ETM6TddvX_9KhSbykjwiinMBgSIsZp8inzyoABN32SEFMg?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=7QDevO) -1. Open a PowerShell terminal. +1. Open a **PowerShell 7** terminal (`pwsh.exe`). 2. Navigate to the directory where the script is located. -3. Execute the script by running: `.\planner_pull.ps1` -4. Follow the on-screen prompts: - * **Choose an option:** Select whether to pull all tasks, only assigned ones, or assigned tasks within a date range. - * **Enter Date Range (if applicable):** If you choose to filter by date, you will be prompted to enter a start and end date in `YYYY-MM-DD` format. - * **Authenticate:** The script uses a device code flow for authentication. You will be prompted to open a URL in a web browser and enter a code to sign in to your Microsoft account. This is required before you can select a plan. - * **Choose a Plan:** - * If you have saved plans in `config.txt`, you will see a numbered list of them. Enter the number to select a plan. - * To add a new plan, choose the "Enter a new Plan ID" option (N). - * **Enter New Plan Details (if applicable):** - * Enter the new Plan ID. - * Enter a descriptive name for the plan. This name is for your reference and will be shown in the selection menu in the future. - * **Enter CSV Filename:** Provide a name for the output CSV file. +3. Execute the script by running: `.\Planner_pull.ps1` +4. Follow the on-screen prompts. + +### Main Menu + +The script presents a main menu with the following options: + +* **1-5 (Data Pulling Options):** Choose how you want to filter the tasks you retrieve. + * 1. Pull all tasks (including unassigned) + * 2. Pull only tasks with assigned users + * 3. Pull tasks of assigned users based on a date range + * 4. Pull tasks from a specific bucket + * 5. Pull tasks by completion status (Not Started, In Progress, Completed) +* **6. Manage saved plans:** Enter a utility menu to add or delete plans from your `config.json` file. +* **Q. Quit:** Exit the script. + +### Data Export Workflow + +After you select a data pulling option (1-5) and retrieve the tasks: + +1. **Data Preview:** A preview of the data is shown in the console. +2. **Choose Export Format:** You will be prompted to choose an export format: **CSV**, **JSON**, or **Markdown**. +3. **Select Columns (for CSV/Markdown):** If you choose CSV or Markdown, a menu will appear listing all available data columns. You can then enter the numbers of the columns you wish to keep in your report (e.g., `1,3,5`). +4. **Enter Filename:** Provide a name for the output file. ## How it Works -1. **Module Check:** The script first checks if the `Microsoft.Graph` module is installed. -2. **User Selection:** It prompts the user to choose between pulling all tasks, only assigned tasks, or assigned tasks within a date range. -3. **Date Range Input:** If the user chooses to filter by date, the script prompts for a start and end date. -4. **Authentication:** It connects to the Microsoft Graph API using a device code authentication flow. This is done early to ensure the script has the necessary permissions for subsequent steps. The script uses a minimal set of permissions required to read tasks and user profiles. -5. **Plan ID Configuration:** - * The script reads the `config.txt` file to find any saved plans. +1. **Pre-flight Checks:** The script first checks for two things: + * That it is being run in **PowerShell 7 or higher**. + * That the `Microsoft.Graph` module is installed. +2. **User Selection:** It prompts the user to choose a filtering method from the main menu. +3. **Authentication:** It connects to the Microsoft Graph API using a device code authentication flow. +4. **Plan ID Configuration (`config.json`):** + * The script reads the `config.json` file to find any saved plans. This file is created automatically. * If saved plans are found, it displays them in a menu for the user to select. - * If the user opts to add a new plan, the script prompts for the new Plan ID and a descriptive name. - * The new plan entry is appended to `config.txt` in the format ` - `. This preserves existing entries. -6. **Data Retrieval:** - * It fetches all tasks for the selected Plan ID using `Get-MgPlannerPlanTask`. - * It fetches all buckets in the plan using `Get-MgPlannerPlanBucket` to create a lookup map of bucket IDs to bucket names. - * For each task, it retrieves detailed information using `Get-MgPlannerTaskDetail`. - * It retrieves user information for assigned users using `Get-MgUser`. -7. **Data Processing:** - * **Date Filtering:** If a date range is provided, the script filters out tasks that were not assigned within that range. - * **Bucket Name:** It matches the task's `bucketId` to the previously fetched list of buckets to get the bucket name. - * **Attachments:** It intelligently scans task references for GitHub links. It checks both the underlying URL and the display text (alias) for each reference. - * If a GitHub link is found in the URL, its display text is checked. If the display text is also a distinct GitHub link, both are preserved for context. Otherwise, only the clean URL is used. - * If a GitHub link is found only in the display text, the entire display text is captured. - This ensures that links are found even if entered incorrectly by users. - * **User Roles:** It determines user roles ("Main Contributor" or "Reviewer") based on the order of assignment. The first assigned user is considered the Main Contributor, and the last is the Reviewer. -8. **Output:** - * The script prompts the user for a desired output CSV filename. - * It compiles all the processed data. - * It displays a preview of the data in the console. - * It exports the final data to the specified CSV file. - -## Output CSV Columns + * The **Manage saved plans** option provides a dedicated utility for adding and deleting plans from this file. +5. **Data Retrieval & Processing:** + * It fetches tasks, buckets, and user details from the Graph API based on the user's filter selections. + * **Attachments:** It intelligently scans task references for GitHub links. + * **User Roles:** It determines user roles ("Main Contributor" or "Reviewer") based on the order of assignment. +6. **Output:** + * The script prompts the user for an export format and an output filename. + * For CSV and Markdown, it allows the user to select specific columns. + * It compiles all the processed data and exports it to the specified file. +7. **Logging:** All operations, user choices, and errors are logged to a file in the `.\logs` directory for easy debugging. + +## Output Columns + +The following data columns are available for export: * **Name:** The display name of the user assigned to the task. * **Role:** The role of the user for that task (Main Contributor, Reviewer, or blank). * **Task:** The title of the Planner task. * **Bucket:** The name of the bucket the task belongs to. -* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. If a reference has a descriptive name that is also a distinct GitHub link, it will be formatted as `DisplayText (URL)`. Otherwise, only the clean URL is shown. This column may also contain a message indicating if no GitHub links were found. +* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. +* **Status:** The completion status of the task (Not Started, In Progress, or Completed). **Date of Creation:** 22/08/2025 **Author:** Ibitope Fatoki - diff --git a/src/content/docs/report-scripts/config.json b/src/content/docs/report-scripts/config.json new file mode 100644 index 00000000..d3144bdd --- /dev/null +++ b/src/content/docs/report-scripts/config.json @@ -0,0 +1,10 @@ +[ + { + "planId": "njykIFLDn0iAY1at7tACfcgADgBS", + "name": "Ontrack Plan id" + }, + { + "planId": "mIelcQoIgkqhbM8WaPS3sMgAEmyV", + "name": "Splashkit Plan id" + } +] diff --git a/src/content/docs/report-scripts/config.txt b/src/content/docs/report-scripts/config.txt deleted file mode 100644 index 6a4c0a41..00000000 --- a/src/content/docs/report-scripts/config.txt +++ /dev/null @@ -1,2 +0,0 @@ -njykIFLDn0iAY1at7tACfcgADgBS - Ontrack Planner ID -mIelcQoIgkqhbM8WaPS3sMgAEmyV - Splashkit Plan ID diff --git a/src/content/docs/report-scripts/logs/script.log b/src/content/docs/report-scripts/logs/script.log new file mode 100644 index 00000000..69009dcb --- /dev/null +++ b/src/content/docs/report-scripts/logs/script.log @@ -0,0 +1,57 @@ +2025-08-23 18:43:37 [INFO] Script started. +2025-08-23 18:43:43 [INFO] Already authenticated as s223739207@deakin.edu.au +2025-08-23 18:43:43 [INFO] Entering plan management utility. +2025-08-23 18:43:45 [INFO] User selected management option: 'a' +2025-08-23 18:43:51 [WARN] User tried to add a plan with empty ID or name. +2025-08-23 18:44:15 [INFO] User selected management option: 'q' +2025-08-23 18:44:15 [INFO] Exiting plan management utility. +2025-08-23 18:44:18 [INFO] Already authenticated as s223739207@deakin.edu.au +2025-08-23 18:44:21 [INFO] User plan selection: '2' +2025-08-23 18:44:21 [INFO] Using Plan ID: mIelcQoIgkqhbM8WaPS3sMgAEmyV +2025-08-23 18:44:48 [INFO] Date filter selected. Start: 07/01/2025 00:00:00, End: 08/24/2025 00:00:00 +2025-08-23 18:44:48 [INFO] Starting Get-PlannerTasks for Plan ID 'mIelcQoIgkqhbM8WaPS3sMgAEmyV'. Selection: 3, BucketId: , Status: +2025-08-23 18:44:49 [INFO] Fetched 120 total tasks from plan. +2025-08-23 18:45:20 [INFO] Finished processing tasks. Returning 120 records. +2025-08-23 18:45:24 [INFO] User chose export format: 1 +2025-08-23 18:45:34 [INFO] Exporting to CSV at 'test.csv' +2025-08-23 18:46:35 [INFO] Already authenticated as s223739207@deakin.edu.au +2025-08-23 18:46:39 [INFO] User plan selection: '2' +2025-08-23 18:46:39 [INFO] Using Plan ID: mIelcQoIgkqhbM8WaPS3sMgAEmyV +2025-08-23 18:47:06 [INFO] Date filter selected. Start: 07/01/2025 00:00:00, End: 08/24/2025 00:00:00 +2025-08-23 18:47:06 [INFO] Starting Get-PlannerTasks for Plan ID 'mIelcQoIgkqhbM8WaPS3sMgAEmyV'. Selection: 3, BucketId: , Status: +2025-08-23 18:47:06 [INFO] Fetched 120 total tasks from plan. +2025-08-23 18:47:39 [INFO] Finished processing tasks. Returning 120 records. +2025-08-23 18:47:43 [INFO] User chose export format: 2 +2025-08-23 18:47:46 [INFO] Exporting to JSON at 'test2.json' +2025-08-23 18:48:10 [INFO] Script finished. +2025-08-23 18:50:43 [INFO] Script started. +2025-08-23 18:50:46 [INFO] Already authenticated as s223739207@deakin.edu.au +2025-08-23 18:50:49 [INFO] User plan selection: '2' +2025-08-23 18:50:49 [INFO] Using Plan ID: mIelcQoIgkqhbM8WaPS3sMgAEmyV +2025-08-23 18:51:11 [INFO] Date filter selected. Start: 07/01/2025 00:00:00, End: 08/02/2025 00:00:00 +2025-08-23 18:51:11 [INFO] Starting Get-PlannerTasks for Plan ID 'mIelcQoIgkqhbM8WaPS3sMgAEmyV'. Selection: 3, BucketId: , Status: +2025-08-23 18:51:12 [INFO] Fetched 120 total tasks from plan. +2025-08-23 18:51:40 [INFO] Finished processing tasks. Returning 44 records. +2025-08-23 18:51:51 [INFO] User chose export format: 3 +2025-08-23 18:52:14 [INFO] User column selection: '1,2,3,5' +2025-08-23 18:52:20 [INFO] Exporting to Markdown at 'test123.md' with columns: Name, Role, Task, Attachments +2025-08-23 18:52:50 [INFO] Script finished. +2025-08-23 18:56:54 [INFO] Script started. +2025-08-23 18:57:11 [INFO] Already authenticated as s223739207@deakin.edu.au +2025-08-23 18:57:14 [INFO] User plan selection: '1' +2025-08-23 18:57:14 [INFO] Using Plan ID: njykIFLDn0iAY1at7tACfcgADgBS +2025-08-23 18:57:42 [INFO] Date filter selected. Start: 07/01/2025 00:00:00, End: 08/24/2025 00:00:00 +2025-08-23 18:57:42 [INFO] Starting Get-PlannerTasks for Plan ID 'njykIFLDn0iAY1at7tACfcgADgBS'. Selection: 3, BucketId: , Status: +2025-08-23 18:57:43 [INFO] Fetched 400 total tasks from plan. +2025-08-23 18:57:58 [INFO] Script started. +2025-08-23 18:58:02 [INFO] Already authenticated as s223739207@deakin.edu.au +2025-08-23 18:58:04 [INFO] User plan selection: '2' +2025-08-23 18:58:04 [INFO] Using Plan ID: mIelcQoIgkqhbM8WaPS3sMgAEmyV +2025-08-23 18:58:19 [INFO] Date filter selected. Start: 07/01/2025 00:00:00, End: 08/23/2025 00:00:00 +2025-08-23 18:58:19 [INFO] Starting Get-PlannerTasks for Plan ID 'mIelcQoIgkqhbM8WaPS3sMgAEmyV'. Selection: 3, BucketId: , Status: +2025-08-23 18:58:20 [INFO] Fetched 120 total tasks from plan. +2025-08-23 18:58:55 [INFO] Finished processing tasks. Returning 119 records. +2025-08-23 18:58:59 [INFO] User chose export format: 1 +2025-08-23 18:59:10 [INFO] User column selection: '' +2025-08-23 18:59:16 [INFO] Exporting to CSV at 'test.csv' +2025-08-23 18:59:27 [INFO] Script finished. diff --git a/src/content/docs/report-scripts/ontrack_tasks.csv b/src/content/docs/report-scripts/ontrack_tasks.csv deleted file mode 100644 index 81145db1..00000000 --- a/src/content/docs/report-scripts/ontrack_tasks.csv +++ /dev/null @@ -1,114 +0,0 @@ -"Name","Role","Task","Bucket","Attachments" -"ALEX BROWN","","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" -"ALEX BROWN","","Migrate groups.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/382" -"ALEX BROWN","","Fix change_remotes.sh","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-deploy/pull/32" -"ALEX BROWN","","Migrate student-task-list.coffee","Frontend Migration","No GitHub links" -"ALEX BROWN","Main Contributor","Migrate runtime.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/398" -"ALEX BROWN","","Migrate privacy-policy.coffee","Frontend Migration","No GitHub links" -"ALEX BROWN","Reviewer","Migrate outcome-service.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/390" -"ALEX BROWN","","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" -"CHELAKA YASODHANA PATHBERIYAGE","Reviewer","Add observer property to unit-level roles","Visitor Enhancement - NOT CAPSTONE","No GitHub links" -"CHELAKA YASODHANA PATHBERIYAGE","","Migrate portfolio-review-step.coffee","Frontend Migration","No GitHub links" -"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migrate group-selector.coffee","Frontend Migration","No GitHub links" -"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migration: task-ilo-alignment-rater","Frontend Migration","https://github.com/thoth-tech/documentation/pull/603; https://github.com/thoth-tech/doubtfire-web/pull/338" -"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" -"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","Main Contributor","Migrate recorder-service.coffee","Sprint 2 Doing","https://github.com/thoth-tech/doubtfire-web/pull/391" -"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migrate outcome-service.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/390" -"DISURU PASANJITH RATHNAYAKE RATHNAYAKE THUDUGALA BANDULAGE","","Migrate Group-member-list","Sprint 1 Doing","https://github.com/thoth-tech/doubtfire-web/pull/393" -"DUY NGUYEN","Main Contributor","Update unit model to host more details","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/71" -"DUY NGUYEN","","Enrich the unit card itself to show more info","CourseFlow","No GitHub links" -"DUY NGUYEN","Reviewer","Migrate runtime.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/398" -"DUY NGUYEN","Reviewer","Migrate groups.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/382" -"DUY NGUYEN","Main Contributor","Refactor CourseFlow code base for better components maintainence","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/385" -"DUY NGUYEN","","Add option to overload units to a teaching period","CourseFlow","No GitHub links" -"DUY NGUYEN","Main Contributor","Create test data for CourseFlow ( Course map, Course map units, Units)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/70" -"EDWARD NGUYEN","Main Contributor","Add summary of skills component","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/396" -"EDWARD NGUYEN","Reviewer","Update unit model to host more details","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/71" -"EDWARD NGUYEN","","Create a component to display credit points achieved.","CourseFlow","No GitHub links" -"EDWARD NGUYEN","","Populate the overlay component for the detailed unit with content of the unit - this needs to include unit requirements.","CourseFlow","https://github.com/thoth-tech/doubtfire-web/pull/215" -"EDWARD NGUYEN","Main Contributor","Add ability to mark unit as complete (phase 4)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/386 (https://github.com/thoth-tech/doubtfire-web/pull/396)" -"EDWARD NGUYEN","","Create test data for CourseFlow ( Course map, Course map units, Units)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/70" -"EDWARD NGUYEN","Reviewer","Remove alignment-bar-chart.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/395" -"EKAM BHULLAR","","Implement ngx graphs/charts in UI","Tutor Times","https://github.com/thoth-tech/doubtfire-web/pull/392" -"EKAM BHULLAR","Reviewer","Add summary of skills component","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/396" -"EKAM BHULLAR","Reviewer","Refactor CourseFlow code base for better components maintainence","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/385" -"EKAM BHULLAR","","Implement Real-Time Notification System","Tutor Times","No GitHub links" -"EKAM BHULLAR","","Migrate task-ilo-alignment-editor.coffee","Frontend Migration","No GitHub links" -"GAURAV MANOHAR MYANA","","Build Dashboard for admin","Tutor Times","No GitHub links" -"GAURAV MANOHAR MYANA","Reviewer","OnTrack Style Guide v0.1","Colour Vision Deficiency Accessibility","No GitHub links" -"HASINDU DESHITHA WELARATHNE","Main Contributor","Migrate students-list.coffee","Frontend Migration","https://github.com/thoth-tech/documentation/pull/597; https://github.com/thoth-tech/doubtfire-web/pull/389" -"HASINDU DESHITHA WELARATHNE","","Migrate recorder-service.coffee","Sprint 2 Doing","https://github.com/thoth-tech/doubtfire-web/pull/391" -"IBI FATOKI","","Create Script to Pull tasks from Planner","Visitor Enhancement - NOT CAPSTONE","https://github.com/thoth-tech/doubtfire-astro/pull/50" -"IBI FATOKI","Main Contributor","Security Issue: Publicly Accessible API Documentation via Swagger","OnTrack x AppAttack","https://github.com/thoth-tech/doubtfire-api/pull/73; https://github.com/thoth-tech/doubtfire-astro/pull/48/files" -"IRIS CHEN","","Web Security Audit: Security Misconfiguration","Security","No GitHub links" -"IRIS CHEN","Main Contributor","Standardize API Authentication Requirements","Security","https://github.com/thoth-tech/doubtfire-api/pull/72" -"IRIS CHEN","","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" -"IRIS CHEN","Main Contributor","Web Security Audit: Cryptographic Failures","Security","https://github.com/thoth-tech/doubtfire-astro/pull/45" -"IRIS CHEN","","Fix Insecure Direct Object References (IDOR) Vulnerability","Security","No GitHub links" -"IRIS CHEN","","Fix IDOR Vulnerability - User Data Access","Security","No GitHub links" -"JASON MARK VELLUCCI","","Migrate runtime.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/398" -"JASON MARK VELLUCCI","Main Contributor","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" -"JASON MARK VELLUCCI","","Migrate summary-task-status-scatter","Frontend Migration","No GitHub links" -"JASON MARK VELLUCCI","","Refactor CourseFlow code base for better components maintainence","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/385" -"JASON MARK VELLUCCI","Reviewer","Clean up src/app/admin/modals/create-unit-modal leftover files","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/388; https://github.com/thoth-tech/doubtfire-web/pull/350" -"JASON MARK VELLUCCI","Main Contributor","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" -"JASON MARK VELLUCCI","Reviewer","8.x and 9.x build documentation","Sprint 2 Code Review","No GitHub links" -"JASON MARK VELLUCCI","Reviewer","Migrate Group-member-list","Sprint 1 Doing","https://github.com/thoth-tech/doubtfire-web/pull/393" -"JASON MARK VELLUCCI","","Update unit model to host more details","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/71" -"JOSEPH KALAYATHANKAL SAJI","","Backend - Implement API Endpoints for SessionActivies and MarkingSession for Analytics","Sprint 2 Doing","No GitHub links" -"JOSEPH KALAYATHANKAL SAJI","Main Contributor","Backend - Create marking_sessions table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/4" -"JOSEPH KALAYATHANKAL SAJI","Main Contributor","OnTrack Style Guide v0.1","Colour Vision Deficiency Accessibility","No GitHub links" -"JOSEPH KALAYATHANKAL SAJI","Reviewer","Backend - Create session_activities table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/3" -"JOSH BEVAN","Reviewer","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" -"JOSH BEVAN","Reviewer","Frontend Migrations: Update frontend migration progress list","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/397" -"JOSH TALEV","Main Contributor","Clean up src/app/admin/modals/create-unit-modal leftover files","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/388; https://github.com/thoth-tech/doubtfire-web/pull/350" -"JOSH TALEV","Main Contributor","Migrate confirmation-modal.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/381" -"JOSH TALEV","Main Contributor","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" -"JOSH TALEV","Main Contributor","Backend - Create session_activities table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/3" -"JOSH TALEV","","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" -"LACHLAN MACKIE ROBINSON","Main Contributor","8.x and 9.x build documentation","Sprint 2 Code Review","No GitHub links" -"LACHLAN MACKIE ROBINSON","Main Contributor","Migrate outcome-service.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/390" -"LACHLAN MACKIE ROBINSON","","Migrate utilService.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/383" -"LACHLAN MACKIE ROBINSON","","Migration: group set selector","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/387; https://github.com/thoth-tech/documentation/pull/620" -"LACHLAN MACKIE ROBINSON","Main Contributor","Remove alignment-bar-chart.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/395" -"LAMIA TABASSUM","","Add observer property to unit-level roles","Visitor Enhancement - NOT CAPSTONE","No GitHub links" -"MARTIN JOHN DOLORES","","Migrate confirmation-modal.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/381" -"MARTIN JOHN DOLORES","Main Contributor","Migrate utilService.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/383" -"MARTIN JOHN DOLORES","","Backend - Create new SessionTracker Service","Sprint 2 Doing","No GitHub links" -"MARTIN JOHN DOLORES","Reviewer","Backend - Create marking_sessions table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/4" -"MARTIN JOHN DOLORES","","Backend - Integrate SessionTracker service across related entities","Tutor Times","No GitHub links" -"MARTIN JOHN DOLORES","Main Contributor","Migrate groups.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/382" -"MARTIN JOHN DOLORES","","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" -"MARTIN JOHN DOLORES","Reviewer","Create test data for CourseFlow ( Course map, Course map units, Units)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/70" -"MARTIN JOHN DOLORES","","Web Security Audit: Cryptographic Failures","Security","https://github.com/thoth-tech/doubtfire-astro/pull/45" -"MARTIN JOHN DOLORES","Reviewer","Standardize API Authentication Requirements","Security","https://github.com/thoth-tech/doubtfire-api/pull/72" -"MARTIN JOHN DOLORES","","Backend - Create session_activities table","Sprint 1 Review","https://github.com/martindolores/doubtfire-api/pull/3" -"MARTIN JOHN DOLORES","Reviewer","Security Issue: Publicly Accessible API Documentation via Swagger","OnTrack x AppAttack","https://github.com/thoth-tech/doubtfire-api/pull/73; https://github.com/thoth-tech/doubtfire-astro/pull/48/files" -"MILLICENT ACHIENG AMOLO","","Fix Frontend Admin Route Access Controls","Security","https://github.com/thoth-tech/doubtfire-astro/pull/49#issue-3340358209" -"PARTH SANJAYKUMAR VAGHELA","","Migrate unit-dates-selector.coffee","Sprint 2 Doing","No GitHub links" -"PASINDU FERNANDO","","Migrate students-list.coffee","Frontend Migration","https://github.com/thoth-tech/documentation/pull/597; https://github.com/thoth-tech/doubtfire-web/pull/389" -"PASINDU FERNANDO","Main Contributor","Migrate Group-member-list","Sprint 1 Doing","https://github.com/thoth-tech/doubtfire-web/pull/393" -"PASINDU FERNANDO","Reviewer","Migrate recorder-service.coffee","Sprint 2 Doing","https://github.com/thoth-tech/doubtfire-web/pull/391" -"PASINDU FERNANDO","Reviewer","Migrate confirmation-modal.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/381" -"PASINDU FERNANDO","Reviewer","Migrate Portfolio Welcome Step","Sprint 1 Complete","https://github.com/thoth-tech/doubtfire-web/pull/384; https://github.com/thoth-tech/documentation/pull/619" -"RASHI AGRAWAL","Reviewer","Frontend: Add new SGE page","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/394" -"RASHI AGRAWAL","","Test backend and frontend integration","Sprint 1 Complete","No GitHub links" -"RASHI AGRAWAL","","Migrate unit-ilo-edit-modal.coffee","Frontend Migration","No GitHub links" -"SAHIRU HESHAN WITHANAGE","","Remove alignment-bar-chart.coffee","Sprint 2 Code Review","https://github.com/thoth-tech/doubtfire-web/pull/395" -"SAHIRU HESHAN WITHANAGE","","Add summary of skills component","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/396" -"SAHIRU HESHAN WITHANAGE","Reviewer","Add ability to mark unit as complete (phase 4)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/386 (https://github.com/thoth-tech/doubtfire-web/pull/396)" -"SAHIRU HESHAN WITHANAGE","","Frontend Work: Student search and selection","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/359" -"SAHIRU HESHAN WITHANAGE","Main Contributor","Frontend: Add new SGE page","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/394" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Frontend: Add new SGE page","Staff Grant Extension","https://github.com/thoth-tech/doubtfire-web/pull/394" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Standardize API Authentication Requirements","Security","https://github.com/thoth-tech/doubtfire-api/pull/72" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Add ability to mark unit as complete (phase 4)","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/386 (https://github.com/thoth-tech/doubtfire-web/pull/396)" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Backend- In-system notification system","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-api/pull/69" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Update documentation in doubtfire-astro for staff grant extension new APIs and FE components","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-astro/pull/40" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","","Frontend Work: Set up notification UI","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/353" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","Reviewer","Migrate utilService.coffee","Sprint 1 Review","https://github.com/thoth-tech/doubtfire-web/pull/383" -"SAMINDI SITUMYA RATNAYAKE MUDIYANSELAGE","Reviewer","Web Security Audit: Cryptographic Failures","Security","https://github.com/thoth-tech/doubtfire-astro/pull/45" -"TRIET LAM","","Migrate task-dashboard.coffee","Frontend Migration","No GitHub links" -"TRIET LAM","","Cleanup feedback.coffee","Frontend Migration","https://github.com/thoth-tech/doubtfire-web/pull/399" -"WAEL ALAHMADI","Main Contributor","Add observer property to unit-level roles","Visitor Enhancement - NOT CAPSTONE","No GitHub links" -"WAEL ALAHMADI","Reviewer","Migrate students-list.coffee","Frontend Migration","https://github.com/thoth-tech/documentation/pull/597; https://github.com/thoth-tech/doubtfire-web/pull/389" -"WAEL ALAHMADI","","Develop Tutor Time Dashboard ","Tutor Times","No GitHub links" From 0616bfd44428362200709968a8e8e22c93e68e3f Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Sat, 23 Aug 2025 19:17:17 +1000 Subject: [PATCH 03/10] doc updates --- src/content/docs/report-scripts/Planner_Pull_Documentation.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/content/docs/report-scripts/Planner_Pull_Documentation.md b/src/content/docs/report-scripts/Planner_Pull_Documentation.md index 26b0693a..41bdce8f 100644 --- a/src/content/docs/report-scripts/Planner_Pull_Documentation.md +++ b/src/content/docs/report-scripts/Planner_Pull_Documentation.md @@ -50,8 +50,6 @@ For a visual guide on how to find the Plan ID, please watch this video (Covers T ## How to Run the Script -[Script Demo](https://deakin365-my.sharepoint.com/:v:/g/personal/s223739207_deakin_edu_au/ETM6TddvX_9KhSbykjwiinMBgSIsZp8inzyoABN32SEFMg?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=7QDevO) - 1. Open a **PowerShell 7** terminal (`pwsh.exe`). 2. Navigate to the directory where the script is located. 3. Execute the script by running: `.\Planner_pull.ps1` From ee13f0b8c65b40ffa0da6f8ff25a0ca565f892a1 Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Sat, 23 Aug 2025 19:37:22 +1000 Subject: [PATCH 04/10] Documentation updates --- .../Planner_Pull_Documentation.md | 156 +++++++++++------- 1 file changed, 99 insertions(+), 57 deletions(-) diff --git a/src/content/docs/report-scripts/Planner_Pull_Documentation.md b/src/content/docs/report-scripts/Planner_Pull_Documentation.md index 41bdce8f..0cdc8d55 100644 --- a/src/content/docs/report-scripts/Planner_Pull_Documentation.md +++ b/src/content/docs/report-scripts/Planner_Pull_Documentation.md @@ -10,19 +10,19 @@ For CSV and Markdown exports, the script allows you to interactively select whic ## Key Features -* **Multiple Filtering Options:** Pull tasks by assignment, date range, completion status, or specific bucket. -* **Multiple Export Formats:** Export your data to CSV, JSON, or a report-ready Markdown table. -* **Customizable Columns:** For CSV and Markdown exports, you can choose exactly which columns to include. -* **Plan Management:** An interactive utility to add, delete, and view your saved Planner plans. -* **Robust Logging:** Creates a detailed log file in the `\logs` directory, with a configurable log level for easier debugging. -* **Environment Checks:** Automatically checks for the required PowerShell version (7+) and `Microsoft.Graph` module to prevent errors. +* **Multiple Filtering Options:** Pull tasks by assignment, date range, completion status, or specific bucket. +* **Multiple Export Formats:** Export your data to CSV, JSON, or a report-ready Markdown table. +* **Customizable Columns:** For CSV and Markdown exports, you can choose exactly which columns to include. +* **Plan Management:** An interactive utility to add, delete, and view your saved Planner plans. +* **Robust Logging:** Creates a detailed log file in the `./logs` directory, with a configurable log level for easier debugging. +* **Environment Checks:** Automatically checks for the required PowerShell version (7+) and `Microsoft.Graph` module to prevent errors. ## Prerequisites -* **PowerShell 7 or higher.** The script will not run on older versions like Windows PowerShell 5.1. You can get PowerShell 7 from the [Microsoft Page](https://learn.microsoft.com/en-gb/powershell/scripting/install/installing-powershell?view=powershell-7.5) or [GitHub](https://github.com/PowerShell/PowerShell). -* An internet connection. -* A Microsoft 365 account with access to Microsoft Planner. -* The `Microsoft.Graph` PowerShell module. +* **PowerShell 7 or higher.** The script will not run on older versions like Windows PowerShell 5.1. You can get PowerShell 7 from the [Microsoft Page](https://learn.microsoft.com/en-gb/powershell/scripting/install/installing-powershell?view=powershell-7.5) or [GitHub](https://github.com/PowerShell/PowerShell). +* An internet connection. +* A Microsoft 365 account with access to Microsoft Planner. +* The `Microsoft.Graph` PowerShell module. ## Setup @@ -36,78 +36,120 @@ Install-Module Microsoft.Graph -Scope CurrentUser -AllowClobber -Force The Plan ID is required to fetch tasks from a specific plan. You can find the Plan ID in the URL of your Planner board. -1. Go to [Microsoft Planner](https://tasks.office.com/). -2. Open the plan you want to use. -3. Look at the URL in your browser's address bar. It will look something like this: - `https://tasks.office.com/your-tenant.com/en-US/Home/Plan?planId=YOUR_PLAN_ID&ownerId=...` -4. The `planId` is the alphanumeric string that follows `planId=`. Copy this value. +1. Go to [Microsoft Planner](https://tasks.office.com/). +2. Open the plan you want to use. +3. Look at the URL in your browser's address bar. It will look something like this: + `https://tasks.office.com/your-tenant.com/en-US/Home/Plan?planId=YOUR_PLAN_ID&ownerId=...` +4. The `planId` is the alphanumeric string that follows `planId=`. Copy this value. ### Video Tutorial -For a visual guide on how to find the Plan ID, please watch this video (Covers Two methods): +For a visual guide on how to find the Plan ID, please watch this video (covers two methods): -[Plan ID Tutorial](https://deakin365-my.sharepoint.com/:v:/g/personal/s223739207_deakin_edu_au/EeAm2dpPc3VGrh6DHzyHkOcBig0my4m3UYWG5HmGtFG09A?nav=eyJyZWZlcnJhbEluZm8iOnsicmVmZXJyYWxBcHAiOiJPbmVEcml2ZUZvckJ1c2luZXNzIiwicmVmZXJyYWxBcHBQbGF0Zm9ybSI6IldlYiIsInJlZmVycmFsTW9kZSI6InZpZXciLCJyZWZlcnJhbFZpZXciOiJNeUZpbGVzTGlua0NvcHkifX0&e=TVSwwN) +[Plan ID Tutorial](https://youtu.be/KdOdRppqxCk) +If you don't want to do that, here are the steps transcribed: + +# How to Find Your Planner ID + +This guide outlines two methods for finding your Planner ID, as described in the video "How to Find your Planner ID." + +### Method 1: Scraping from the URL + +This method is simpler but less reliable. + +1. **Open your Planner:** Go to the specific plan in your web browser. +2. **Locate the ID:** Look at the address bar. The **Planner ID** is the string of characters right after `"plan/"`. +3. **Copy the ID:** This alphanumeric string is your Planner ID. + +### Method 2: Using Microsoft Graph Explorer + +This method is more reliable but more complex. + +1. **Go to Graph Explorer:** Open `developer.microsoft.com/graph/graph-explorer` in your browser. + +#### Option A: If you have a task assigned to the plan + +1. In the left sidebar, scroll down and expand **Users**. +2. Find and click on **all my planner tasks**. +3. Click the **Run query** button. +4. In the response area, find **planId**. The value next to it is your Planner ID. + +#### Option B: If you do not have a task assigned to the plan + +**First, get the Group ID:** +1. In the left sidebar, scroll up to **Groups**. +2. Click on **all groups I belong to**. +3. Click **Run query**. +4. Use your browser's find function (Ctrl+F or Cmd+F) to search for the name of the group associated with your plan. +5. Locate the **id** field next to the group name. This is your **Group ID**. Copy it. + +**Next, get the Planner ID using the Group ID:** +1. In the left sidebar, scroll down to **Planner**. +2. Click on **all Planner plans associated with a group**. +3. In the query bar, replace the placeholder with the Group ID you just copied. +4. Click **Run query**. +5. In the response, find the plan by its **title**. The **id** field associated with that title is your **Planner ID**. ## How to Run the Script -1. Open a **PowerShell 7** terminal (`pwsh.exe`). -2. Navigate to the directory where the script is located. -3. Execute the script by running: `.\Planner_pull.ps1` -4. Follow the on-screen prompts. +1. Open a **PowerShell 7** terminal (`pwsh.exe`). +2. Navigate to the directory where the script is located. +3. Execute the script by running: `./Planner_pull.ps1` +4. Follow the on-screen prompts. ### Main Menu The script presents a main menu with the following options: -* **1-5 (Data Pulling Options):** Choose how you want to filter the tasks you retrieve. - * 1. Pull all tasks (including unassigned) - * 2. Pull only tasks with assigned users - * 3. Pull tasks of assigned users based on a date range - * 4. Pull tasks from a specific bucket - * 5. Pull tasks by completion status (Not Started, In Progress, Completed) -* **6. Manage saved plans:** Enter a utility menu to add or delete plans from your `config.json` file. -* **Q. Quit:** Exit the script. +* **1-5 (Data Pulling Options):** Choose how you want to filter the tasks you retrieve: + 1. Pull all tasks (including unassigned) + 2. Pull only tasks with assigned users + 3. Pull tasks of assigned users based on a date range + 4. Pull tasks from a specific bucket + 5. Pull tasks by completion status (Not Started, In Progress, Completed) +* **6. Manage saved plans:** Enter a utility menu to add or delete plans from your `config.json` file. +* **Q. Quit:** Exit the script. ### Data Export Workflow After you select a data pulling option (1-5) and retrieve the tasks: -1. **Data Preview:** A preview of the data is shown in the console. -2. **Choose Export Format:** You will be prompted to choose an export format: **CSV**, **JSON**, or **Markdown**. -3. **Select Columns (for CSV/Markdown):** If you choose CSV or Markdown, a menu will appear listing all available data columns. You can then enter the numbers of the columns you wish to keep in your report (e.g., `1,3,5`). -4. **Enter Filename:** Provide a name for the output file. +1. **Data Preview:** A preview of the data is shown in the console. +2. **Choose Export Format:** You will be prompted to choose an export format: **CSV**, **JSON**, or **Markdown**. +3. **Select Columns (for CSV/Markdown):** If you choose CSV or Markdown, a menu will appear listing all available data columns. You can then enter the numbers of the columns you wish to keep in your report (e.g., `1,3,5`). +4. **Enter Filename:** Provide a name for the output file. ## How it Works -1. **Pre-flight Checks:** The script first checks for two things: - * That it is being run in **PowerShell 7 or higher**. - * That the `Microsoft.Graph` module is installed. -2. **User Selection:** It prompts the user to choose a filtering method from the main menu. -3. **Authentication:** It connects to the Microsoft Graph API using a device code authentication flow. -4. **Plan ID Configuration (`config.json`):** - * The script reads the `config.json` file to find any saved plans. This file is created automatically. - * If saved plans are found, it displays them in a menu for the user to select. - * The **Manage saved plans** option provides a dedicated utility for adding and deleting plans from this file. -5. **Data Retrieval & Processing:** - * It fetches tasks, buckets, and user details from the Graph API based on the user's filter selections. - * **Attachments:** It intelligently scans task references for GitHub links. - * **User Roles:** It determines user roles ("Main Contributor" or "Reviewer") based on the order of assignment. -6. **Output:** - * The script prompts the user for an export format and an output filename. - * For CSV and Markdown, it allows the user to select specific columns. - * It compiles all the processed data and exports it to the specified file. -7. **Logging:** All operations, user choices, and errors are logged to a file in the `.\logs` directory for easy debugging. +1. **Pre-flight Checks:** The script first checks for two things: + * That it is being run in **PowerShell 7 or higher**. + * That the `Microsoft.Graph` module is installed. +2. **User Selection:** It prompts the user to choose a filtering method from the main menu. +3. **Authentication:** It connects to the Microsoft Graph API using a device code authentication flow. +4. **Plan ID Configuration (`config.json`):** + * The script reads the `config.json` file to find any saved plans. This file is created automatically. + * If saved plans are found, it displays them in a menu for the user to select. + * The **Manage saved plans** option provides a dedicated utility for adding and deleting plans from this file. +5. **Data Retrieval & Processing:** + * It fetches tasks, buckets, and user details from the Graph API based on the user's filter selections. + * **Attachments:** It intelligently scans task references for GitHub links. + * **User Roles:** It determines user roles ("Main Contributor" or "Reviewer") based on the order of assignment. +6. **Output:** + * The script prompts the user for an export format and an output filename. + * For CSV and Markdown, it allows the user to select specific columns. + * It compiles all the processed data and exports it to the specified file. +7. **Logging:** All operations, user choices, and errors are logged to a file in the `./logs` directory for easy debugging. ## Output Columns The following data columns are available for export: -* **Name:** The display name of the user assigned to the task. -* **Role:** The role of the user for that task (Main Contributor, Reviewer, or blank). -* **Task:** The title of the Planner task. -* **Bucket:** The name of the bucket the task belongs to. -* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. -* **Status:** The completion status of the task (Not Started, In Progress, or Completed). +* **Name:** The display name of the user assigned to the task. +* **Role:** The role of the user for that task (Main Contributor, Reviewer, or blank). +* **Task:** The title of the Planner task. +* **Bucket:** The name of the bucket the task belongs to. +* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. +* **Status:** The completion status of the task (Not Started, In Progress, or Completed). **Date of Creation:** 22/08/2025 **Author:** Ibitope Fatoki From 1de213a54b42a9585fde941d796e7eff7da9cb12 Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Thu, 4 Sep 2025 18:26:28 +1000 Subject: [PATCH 05/10] new features and QOL improvements. --- .../docs/report-scripts/Planner_Pull.ps1 | 141 +++++++++++++++++- .../Planner_Pull_Documentation.md | 30 +++- 2 files changed, 159 insertions(+), 12 deletions(-) diff --git a/src/content/docs/report-scripts/Planner_Pull.ps1 b/src/content/docs/report-scripts/Planner_Pull.ps1 index f24ce06b..23109132 100644 --- a/src/content/docs/report-scripts/Planner_Pull.ps1 +++ b/src/content/docs/report-scripts/Planner_Pull.ps1 @@ -204,7 +204,7 @@ function Save-Configurations { } } -function Manage-Configurations { +function Set-Configurations { param ([string]$ConfigPath) Write-Log -Level INFO -Message "Entering plan management utility." @@ -230,6 +230,7 @@ function Manage-Configurations { switch ($choice.ToUpper()) { 'A' { + Write-Host "`nNeed help finding the id? Look no further https://youtu.be/KdOdRppqxCk" $newPlanId = Read-Host -Prompt "Please enter the new Plan ID" $planName = Read-Host -Prompt "Please enter a name for this plan" @@ -294,6 +295,7 @@ function Select-PlannerPlan { if ($savedPlans.Count -gt 0) { Write-Host "Please choose a saved Plan or enter a new one:" + Write-Host "`nNeed help finding the id? Look no further https://youtu.be/KdOdRppqxCk" for ($i = 0; $i -lt $savedPlans.Count; $i++) { Write-Host ("{0}. {1}" -f ($i + 1), $savedPlans[$i].name) } @@ -436,8 +438,8 @@ function Get-PlannerTasks { param ( [string]$PlanId, [string]$Selection, - [datetime]$StartDate, - [datetime]$EndDate, + $StartDate, + $EndDate, [string]$BucketId, [string]$Status ) @@ -622,6 +624,12 @@ function Export-Data { $markdownTable = ConvertTo-MarkdownTable -Data $reportData Set-Content -Path $outputFile -Value $markdownTable Write-Host "Tasks exported successfully to $outputFile" + + $convertChoice = Read-Host -Prompt "Convert GitHub PR links to clickable Markdown hyperlinks? (y/n)" + if ($convertChoice.ToUpper() -eq 'Y') { + Convert-GitHubLinksToMarkdown -Path $outputFile + Write-Host "GitHub links have been converted in $outputFile" + } } catch { Write-Log -Level "ERROR" -Message "Failed to export Markdown to '$outputFile': $_" @@ -635,6 +643,117 @@ function Export-Data { } } +function Convert-GitHubLinksToMarkdown { + param ( + [string]$Path + ) + Write-Log -Level INFO -Message "Starting GitHub link conversion for '$Path'." + try { + $content = Get-Content -Path $Path -Raw + + # Regex to find GitHub pull request URLs. + # It captures the whole URL and then the PR number. + $regex = '(https?://github\.com/[^/|]+/[^/|]+/pull/(\d+))' + + $newContent = $content + $foundMatches = $content | Select-String -Pattern $regex -AllMatches + + if ($foundMatches) { + # Get unique matches to avoid processing the same URL multiple times + $uniqueMatches = $foundMatches.Matches | Select-Object -Property Value, @{N='PR';E={$_.Groups[2].Value}} -Unique + + foreach ($match in $uniqueMatches) { + $url = $match.Value + $pr = $match.PR + $markdownLink = "[PR#$pr]($url)" + # Use simple string replacement. + $newContent = $newContent.Replace($url, $markdownLink) + } + } + + $newContent | Set-Content -Path $Path + Write-Log -Level INFO -Message "Finished GitHub link conversion for '$Path'." + } + catch { + Write-Log -Level "ERROR" -Message "Failed to convert GitHub links in '$Path'. Error: $_" + Write-Error "An error occurred during link conversion: $_" + } +} + +function Convert-CsvToMarkdown { + Write-Log -Level INFO -Message "Entering CSV to Markdown conversion utility." + + $csvPath = $null # Initialize to null + + # Find CSV files in the script's directory ($PSScriptRoot is an automatic variable) + $localCsvFiles = Get-ChildItem -Path $PSScriptRoot -Filter *.csv + + if ($localCsvFiles.Count -gt 0) { + Write-Host "`nFound CSV files in the current directory:" + for ($i = 0; $i -lt $localCsvFiles.Count; $i++) { + Write-Host ("{0}. {1}" -f ($i + 1), $localCsvFiles[$i].Name) + } + Write-Host "M. Enter a file path manually" + + $choice = Read-Host -Prompt "Select a file or choose manual entry" + + if ($choice -match "^\d+$" -and [int]$choice -ge 1 -and [int]$choice -le $localCsvFiles.Count) { + $selectedIndex = [int]$choice - 1 + $csvPath = $localCsvFiles[$selectedIndex].FullName + Write-Log -Level INFO -Message "User selected local CSV file: $csvPath" + } + elseif ($choice.ToUpper() -ne 'M') { + Write-Host "Invalid selection. Returning to main menu." + Write-Log -Level WARN -Message "User made an invalid selection: $choice" + return + } + # If choice is 'M', $csvPath remains null and we fall through to the manual prompt. + } + + # If no local files were found, or if user chose manual entry + if (-not $csvPath) { + $csvPath = Read-Host -Prompt "Please enter the path to the input CSV file" + } + + # Validate the final path + if (-not (Test-Path -Path $csvPath) -or -not ($csvPath.EndsWith(".csv"))) { + Write-Log -Level WARN -Message "Invalid CSV path provided: $csvPath" + Write-Host "Error: The specified file does not exist or is not a .csv file." + return + } + + $markdownPath = Read-Host -Prompt "Please enter the name for the output Markdown file" + if (-not ($markdownPath.EndsWith(".md"))) { + $markdownPath = "$markdownPath.md" + } + + try { + $csvData = Import-Csv -Path $csvPath + + if (-not $csvData) { + Write-Log -Level WARN -Message "CSV file '$csvPath' is empty or could not be read." + Write-Host "Warning: CSV file is empty or could not be read. No output generated." + return + } + + $markdownTable = ConvertTo-MarkdownTable -Data $csvData + Set-Content -Path $markdownPath -Value $markdownTable + Write-Log -Level INFO -Message "Successfully converted '$csvPath' to '$markdownPath'." + Write-Host "Successfully converted CSV to Markdown: $markdownPath" + + # Offer to convert GitHub links + $convertChoice = Read-Host -Prompt "Convert GitHub PR links to clickable Markdown hyperlinks? (y/n)" + if ($convertChoice.ToUpper() -eq 'Y') { + Convert-GitHubLinksToMarkdown -Path $markdownPath + Write-Host "GitHub links have been converted in $markdownPath" + } + } + catch { + Write-Log -Level "ERROR" -Message "Failed to convert CSV '$csvPath' to Markdown. Error: $_" + Write-Error "An error occurred during CSV to Markdown conversion: $_" + } +} + # --- Main Script --- # --- Pre-flight Checks --- @@ -664,6 +783,7 @@ while ($true) { Write-Host "4. Pull tasks from a specific bucket" Write-Host "5. Pull tasks by completion status" Write-Host "6. Manage saved plans" + Write-Host "7. Convert CSV to Markdown" Write-Host "Q. Quit" $selection = Read-Host -Prompt "Enter your choice" @@ -671,19 +791,28 @@ while ($true) { break } - if ($selection -notin '1', '2', '3', '4', '5', '6') { + # Handle standalone options first + if ($selection -eq '7') { + Convert-CsvToMarkdown + Read-Host "Press Enter to return to the main menu" + continue + } + + if ($selection -notin '1', '2', '3', '4', '5', '6', '7') { Write-Log -Level WARN -Message "User selected invalid main menu option: $selection" Write-Host "Invalid selection." continue } + # The rest of the options require Graph connection Connect-ToGraph if ($selection -eq '6') { - Manage-Configurations -ConfigPath $ConfigPath + Set-Configurations -ConfigPath $ConfigPath continue } + # The rest of the options require a Plan ID $planId = Select-PlannerPlan -ConfigPath $ConfigPath if (-not $planId) { continue } @@ -701,7 +830,7 @@ while ($true) { Write-Log -Level INFO -Message "Date filter selected. Start: $startDate, End: $endDate" } catch { - Write-Log -Level WARN -Message "User entered invalid date format." + Write-Log -Level WARN -Message "User entered invalid date format $startDateStr and $endDateStr." Write-Host "Invalid date format. Please use YYYY-MM-DD." continue } diff --git a/src/content/docs/report-scripts/Planner_Pull_Documentation.md b/src/content/docs/report-scripts/Planner_Pull_Documentation.md index 0cdc8d55..2af3ba37 100644 --- a/src/content/docs/report-scripts/Planner_Pull_Documentation.md +++ b/src/content/docs/report-scripts/Planner_Pull_Documentation.md @@ -1,4 +1,6 @@ -# Planner Task Puller Script Documentation +--- +title: Planner Task Puller Script Documentation +--- ## Overview @@ -12,6 +14,8 @@ For CSV and Markdown exports, the script allows you to interactively select whic * **Multiple Filtering Options:** Pull tasks by assignment, date range, completion status, or specific bucket. * **Multiple Export Formats:** Export your data to CSV, JSON, or a report-ready Markdown table. +* **CSV to Markdown Utility:** A dedicated tool to convert existing CSV files into Markdown tables, with an easy-to-use file picker for local files. +* **Automatic Link Conversion:** For Markdown exports, automatically convert raw GitHub pull request URLs into formatted, clickable Markdown hyperlinks. * **Customizable Columns:** For CSV and Markdown exports, you can choose exactly which columns to include. * **Plan Management:** An interactive utility to add, delete, and view your saved Planner plans. * **Robust Logging:** Creates a detailed log file in the `./logs` directory, with a configurable log level for easier debugging. @@ -94,7 +98,7 @@ This method is more reliable but more complex. 1. Open a **PowerShell 7** terminal (`pwsh.exe`). 2. Navigate to the directory where the script is located. -3. Execute the script by running: `./Planner_pull.ps1` +3. Execute the script by running: `.\Planner_pull.ps1` 4. Follow the on-screen prompts. ### Main Menu @@ -108,6 +112,7 @@ The script presents a main menu with the following options: 4. Pull tasks from a specific bucket 5. Pull tasks by completion status (Not Started, In Progress, Completed) * **6. Manage saved plans:** Enter a utility menu to add or delete plans from your `config.json` file. +* **7. Convert CSV to Markdown:** A tool to convert a CSV file to a Markdown table. * **Q. Quit:** Exit the script. ### Data Export Workflow @@ -118,14 +123,26 @@ After you select a data pulling option (1-5) and retrieve the tasks: 2. **Choose Export Format:** You will be prompted to choose an export format: **CSV**, **JSON**, or **Markdown**. 3. **Select Columns (for CSV/Markdown):** If you choose CSV or Markdown, a menu will appear listing all available data columns. You can then enter the numbers of the columns you wish to keep in your report (e.g., `1,3,5`). 4. **Enter Filename:** Provide a name for the output file. +5. **Convert Links (for Markdown):** If you chose Markdown, you will be asked if you want to convert GitHub pull request links into clickable Markdown hyperlinks. + +### CSV to Markdown Conversion + +This utility provides a convenient way to create a Markdown report from a CSV file. This is useful if you prefer to export your data to CSV, make manual edits, and then generate a final Markdown table. + +When you select this option from the main menu: + +1. **File Selection:** The script will first look for any `.csv` files in its current directory and display them as a numbered list. You can simply select a number to choose your file. +2. **Manual Path:** If your file is in a different location, you can choose the option to enter the file path manually. +3. **Output:** You will be prompted to provide a name for the new Markdown file. +4. **Link Conversion:** After the Markdown file is created, the script will ask if you want to perform the final step of converting any GitHub URLs into clickable hyperlinks. ## How it Works 1. **Pre-flight Checks:** The script first checks for two things: * That it is being run in **PowerShell 7 or higher**. * That the `Microsoft.Graph` module is installed. -2. **User Selection:** It prompts the user to choose a filtering method from the main menu. -3. **Authentication:** It connects to the Microsoft Graph API using a device code authentication flow. +2. **User Selection:** It prompts the user to choose an action from the main menu. This can be a data pull, plan management, or a utility like the CSV to Markdown converter. +3. **Authentication:** For data pulling and plan management, it connects to the Microsoft Graph API using a device code authentication flow. 4. **Plan ID Configuration (`config.json`):** * The script reads the `config.json` file to find any saved plans. This file is created automatically. * If saved plans are found, it displays them in a menu for the user to select. @@ -137,6 +154,7 @@ After you select a data pulling option (1-5) and retrieve the tasks: 6. **Output:** * The script prompts the user for an export format and an output filename. * For CSV and Markdown, it allows the user to select specific columns. + * If the user exports to Markdown, it offers an additional step to automatically format any found GitHub PR links into proper Markdown hyperlinks. * It compiles all the processed data and exports it to the specified file. 7. **Logging:** All operations, user choices, and errors are logged to a file in the `./logs` directory for easy debugging. @@ -148,8 +166,8 @@ The following data columns are available for export: * **Role:** The role of the user for that task (Main Contributor, Reviewer, or blank). * **Task:** The title of the Planner task. * **Bucket:** The name of the bucket the task belongs to. -* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. +* **Attachments:** A semicolon-separated list of GitHub URLs found in the task's references. In Markdown exports, these can be automatically converted to clickable hyperlinks (e.g., `[PR#123](...)`). * **Status:** The completion status of the task (Not Started, In Progress, or Completed). **Date of Creation:** 22/08/2025 -**Author:** Ibitope Fatoki +**Author:** Ibitope Fatoki \ No newline at end of file From 19c04c505dd6f0b7a9dc089425a0703aa9b88195 Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Fri, 5 Sep 2025 19:56:30 +1000 Subject: [PATCH 06/10] regex update to capture entire links --- src/content/docs/report-scripts/Planner_Pull.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/report-scripts/Planner_Pull.ps1 b/src/content/docs/report-scripts/Planner_Pull.ps1 index 23109132..f9fbc709 100644 --- a/src/content/docs/report-scripts/Planner_Pull.ps1 +++ b/src/content/docs/report-scripts/Planner_Pull.ps1 @@ -653,7 +653,7 @@ function Convert-GitHubLinksToMarkdown { # Regex to find GitHub pull request URLs. # It captures the whole URL and then the PR number. - $regex = '(https?://github\.com/[^/|]+/[^/|]+/pull/(\d+))' + $regex = '(https?://github\.com/[^/|]+/[^/|]+/pull/(\d+)[^|; ]*)' $newContent = $content $foundMatches = $content | Select-String -Pattern $regex -AllMatches From d8b3bb3c2fe844b040ae8d76e51195fe095ec46f Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Wed, 24 Sep 2025 16:39:40 +1000 Subject: [PATCH 07/10] fixed renaming issue --- .../docs/BACaudit/{AuditReport_BAC.md => AuditReportBAC.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/content/docs/BACaudit/{AuditReport_BAC.md => AuditReportBAC.md} (100%) diff --git a/src/content/docs/BACaudit/AuditReport_BAC.md b/src/content/docs/BACaudit/AuditReportBAC.md similarity index 100% rename from src/content/docs/BACaudit/AuditReport_BAC.md rename to src/content/docs/BACaudit/AuditReportBAC.md From 9b078e349a1511ac81e2feefcb31c690cee3adc2 Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Wed, 24 Sep 2025 16:51:59 +1000 Subject: [PATCH 08/10] Modified git ignore to ignore script logs --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6240da8b..730172f7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* - +script.log* # environment variables .env From e1d3d32b82756c8f28ca55e47e4e6fe3fa191edc Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Sat, 27 Sep 2025 19:29:59 +1000 Subject: [PATCH 09/10] token validation check before running --- .../docs/report-scripts/Planner_Pull.ps1 | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/content/docs/report-scripts/Planner_Pull.ps1 b/src/content/docs/report-scripts/Planner_Pull.ps1 index f9fbc709..130d7cf1 100644 --- a/src/content/docs/report-scripts/Planner_Pull.ps1 +++ b/src/content/docs/report-scripts/Planner_Pull.ps1 @@ -139,10 +139,22 @@ function Connect-ToGraph { try { $context = Get-MgContext if ($context) { - Write-Log -Level INFO -Message "Already authenticated as $($context.Account)" - Write-Host "Already authenticated as $($context.Account)" - return $true + try { + Write-Log -Level INFO -Message "Already authenticated as $($context.Account). Verifying token..." + # Make a lightweight call to check if the token is still valid. + Get-MgUser -UserId "me" -ErrorAction Stop -Select Id | Out-Null + Write-Log -Level INFO -Message "Token is still valid." + Write-Host "Already authenticated as $($context.Account)" + return $true + } + catch { + Write-Log -Level WARN -Message "Token validation failed. It might be expired. Attempting to re-authenticate. Error: $_" + Write-Host "Your previous session may have expired. Re-authenticating..." + Disconnect-MgGraph + # Fall-through to re-authenticate + } } + Connect-MgGraph -Scopes @("Tasks.Read", "Tasks.ReadWrite", "User.ReadBasic.All") -UseDeviceCode -Audience "organizations" $context = Get-MgContext if (-not $context) { From 4006b308fb60b8bb9f5fb27fc41868e1487056cc Mon Sep 17 00:00:00 2001 From: Ibitope Fatoki Date: Sat, 27 Sep 2025 21:40:30 +1000 Subject: [PATCH 10/10] switched to browser auth and improved token val --- .../docs/report-scripts/Planner_Pull.ps1 | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/content/docs/report-scripts/Planner_Pull.ps1 b/src/content/docs/report-scripts/Planner_Pull.ps1 index 130d7cf1..cb65bb5f 100644 --- a/src/content/docs/report-scripts/Planner_Pull.ps1 +++ b/src/content/docs/report-scripts/Planner_Pull.ps1 @@ -126,36 +126,46 @@ function Connect-ToGraph { Write-Log -Level DEBUG -Message "Entering Connect-ToGraph function." Write-Host "Importing Required Microsoft.Graph sub modules..." try { + Import-Module Microsoft.Graph.Authentication -ErrorAction Stop Import-Module Microsoft.Graph.Planner -ErrorAction Stop Import-Module Microsoft.Graph.Users -ErrorAction Stop } catch { Write-Log -Level "ERROR" -Message "Failed to import required modules: $_" - Write-Error "Failed to import required modules. Please ensure Microsoft.Graph is installed correctly." + Write-Error "Failed to import required modules. Please ensure Microsoft.Graph is installed correctly and is up to date." exit 1 } Write-Host "Authenticating to Microsoft Graph..." try { $context = Get-MgContext - if ($context) { + if ($context -and $context.AuthContext) { try { - Write-Log -Level INFO -Message "Already authenticated as $($context.Account). Verifying token..." - # Make a lightweight call to check if the token is still valid. - Get-MgUser -UserId "me" -ErrorAction Stop -Select Id | Out-Null - Write-Log -Level INFO -Message "Token is still valid." - Write-Host "Already authenticated as $($context.Account)" - return $true + # Actively test the token to ensure it's valid, as cached tokens can become stale. + Write-Log -Level DEBUG -Message "Existing context found. Verifying token with a test call..." + Get-MgUser -UserId "me" -Property "Id" -ErrorAction Stop | Out-Null + + $tokenExpiry = [datetime]$context.AuthContext.ExpiresOn + $timeToExpiry = $tokenExpiry - (Get-Date) + + if ($timeToExpiry.TotalMinutes -gt 5) { + Write-Log -Level INFO -Message "Already authenticated as $($context.Account). Token is valid." + Write-Host "Already authenticated as $($context.Account)" + return $true + } + else { + Write-Log -Level WARN -Message "Token is expired or expiring soon. Re-authenticating." + Write-Host "Your session is expired or expiring soon. Re-authenticating..." + Disconnect-MgGraph + } } catch { - Write-Log -Level WARN -Message "Token validation failed. It might be expired. Attempting to re-authenticate. Error: $_" - Write-Host "Your previous session may have expired. Re-authenticating..." + Write-Log -Level WARN -Message "Token validation failed or session is invalid. Re-authenticating. Error: $_" Disconnect-MgGraph - # Fall-through to re-authenticate } } - Connect-MgGraph -Scopes @("Tasks.Read", "Tasks.ReadWrite", "User.ReadBasic.All") -UseDeviceCode -Audience "organizations" + Connect-MgGraph -Scopes @("Tasks.Read", "Tasks.ReadWrite", "User.Read", "User.ReadBasic.All") -Audience "organizations" $context = Get-MgContext if (-not $context) { throw "Failed to establish connection" @@ -493,8 +503,14 @@ function Get-PlannerTasks { Write-Log -Level DEBUG -Message "Created lookup for $($buckets.Count) buckets." } catch { - Write-Log -Level "ERROR" -Message "Failed to get tasks or buckets for plan '$PlanId': $_" - Write-Error "Failed to get tasks or buckets: $_" + $errorMessage = $_.Exception.Message + Write-Log -Level "ERROR" -Message "Failed to get tasks or buckets for plan '$PlanId': $errorMessage" + if ($errorMessage -like "*DeviceCodeCredential*") { + Write-Error "An authentication error occurred while fetching data. This can sometimes be resolved by restarting the script to force re-authentication. Error: $errorMessage" + } + else { + Write-Error "Failed to get tasks or buckets: $errorMessage" + } exit 1 } @@ -785,6 +801,9 @@ if (-not (Get-Module -ListAvailable -Name Microsoft.Graph)) { exit } +#clear the previous connection for better reliability +Disconnect-MgGraph + Write-Log -Message "Script started." while ($true) { @@ -859,7 +878,6 @@ while ($true) { Read-Host "Press Enter to return to the main menu" } - Write-Log -Message "Script finished." Write-Host "Script made by Ibitope Fatoki. Github ibi420." Write-Host "Exiting."