Noveris.TrelloTaskMgmt.psm1
################ # Global settings $InformationPreference = "Continue" $ErrorActionPreference = "Stop" Set-StrictMode -Version 2 #Requires -Modules @{"ModuleName"="Noveris.TrelloApi";"RequiredVersion"="0.2.0"} #Requires -Modules @{"ModuleName"="Noveris.SvcProc";"RequiredVersion"="0.1.3"} <# #> Function Select-TrelloListMatches { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNull()] [DateTime]$Date, [Parameter(Mandatory=$true,ValueFromPipeline)] [AllowNull()] [PSCustomObject]$Template ) process { # Skip if this entry if null if ($Template -eq $null) { return } # Ignore if the name is missing or empty if (($Template | Get-Member).Name -notcontains "name" -or [string]::IsNullOrEmpty($Template.name)) { Write-Warning "Missing or empty name in template list" return } $name = $Template.name # Extract the elements of the list name $elements = $null try { $elements = Format-TrelloListName -ListName $name } catch { Write-Warning "Parsing list name failed: $_" return } # Ignore if the date pattern doesn't match $dateStr = ("{0}_{1}_{2}" -f $Date.ToString("yyyyMMdd"), ([int]($Date.DayOfWeek)), ([int]($Date.DayOfYear))) if ($dateStr -notmatch $elements.Pattern) { return } $elements | Add-Member -NotePropertyName list -NotePropertyValue $Template $elements } } <# #> Function Format-TrelloCardName { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$CardName ) process { # Default values, if not specified $list = "Todo" $grace = 0 # Split name in to components $components = $CardName.Split(":") # If there are three or more components, extract in to list and grace as well if (($components | Measure-Object).Count -eq 2) { # No target list name supplied $grace = [int]::Parse($components[0]) $name = $components[1] } elseif (($components | Measure-Object).Count -ge 3) { $list = $components[0] # Let any parser error flow to caller $grace = [int]::Parse($components[1]) $name = $components[2] } else { $name = $CardName } [PSCustomObject]@{ List = $list Grace = $grace Name = $name } } } <# #> Function Format-TrelloListName { param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ListName ) process { # Separate based on last index of the separator, allowing the separator to exist in the pattern, if required in the future $index = $ListName.LastIndexOf(":") # Check if the separator exists in the list name if ($index -lt 0) { Write-Error ("Found list with invalid name: " + $list.name) } # Extract components from list name $pattern = $ListName.Substring(0, $index) $name = $ListName.Substring($index+1) # Check for missing or empty components if ([string]::IsNullOrEmpty($pattern) -or [string]::IsNullOrEmpty($name)) { Write-Error "Missing or empty components in list name" } # Return custom object with components [PSCustomObject]@{ Pattern = $pattern Name = $name } } } <# #> Function Test-TrelloValidConfiguration { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNull()] $Config ) process { "Name", "TemplateBoardId", "TargetBoardId" | ForEach-Object { $prop = $_ if (($config | Get-Member).Name -notcontains $prop -or [string]::IsNullOrEmpty($config.$prop)) { Write-Error "Missing $prop in configuration" } } } } <# #> Function Update-TrelloTasksFromTemplate { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNull()] $Session, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$Name, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$TemplateBoardId, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$TargetBoardId, [Parameter(Mandatory=$false)] [ValidateNotNullOrEmpty()] [string]$StatusListName = "Status" ) process { # Make sure we have a valid session Test-TrelloValidSession -Session $Session $now = [DateTime]::Now $today = [DateTime]::new($now.Year, $now.Month, $now.Day, 0, 0, 0, [System.DateTimeKind]::Local) # Retrieve the 'Status' list from the board. Create if it doesn't exist Write-Verbose "Retrieving status list." $statusList = Get-TrelloLists -Session $session -BoardId $TargetBoardId -FilterFirstNameMatch $StatusListName if (($statusList | Measure-Object).Count -lt 1) { Write-Verbose "Status list not found. Creating." $statusList = Add-TrelloList -Session $session -BoardId $TargetBoardId -Name $StatusListName } # Display status list information Write-Verbose ("'Status' list has id: " + $statusList.id) # Retrieve the 'Last Processed' status card from the list $lastProcessed = Get-TrelloListCards -Session $session -ListId $statusList.id -FilterFirstNameRegex "^Last Processed" # Check if we have a last processed card if (($lastProcessed | Measure-Object).Count -ne 1) { Write-Verbose "Failed to find 'Last Processed' card. Creating." $name = "Last Processed: {0}" -f ($now.ToString("yyyy/MM/dd HH:mm")) $desc = "Next Process: {0}" -f ($today.ToString("o")) $lastProcessed = Add-TrelloListCard -Session $session -ListId $statusList.id -Name $name -Description $desc } Write-Verbose ("Last Procedded Card: {0}" -f $lastProcessed.name) # Determine the next processing date $nextProcess = $today try { $str = $lastProcessed.desc.Split([Environment]::Newline) | Where-Object {$_.StartsWith("Next Process: ")} | Select-Object -First 1 $str = $str.Substring("Next Process: ".Length) $nextProcess = [DateTime]::Parse($str) } catch { Write-Warning "Failed to parse Next Process time from Last Processed card: $_" } Write-Verbose ("Next Processing Date: {0}" -f $nextProcess) # Read all of the lists in the source board Write-Verbose "Reading all template lists" $templateLists = Get-TrelloLists -Session $session -BoardId $TemplateBoardId # Read all of the cards in the source board Write-Verbose "Reading all template cards" $templateCards = Get-TrelloBoardCards -Session $session -BoardId $TemplateBoardId # For each day since the 'Next Process' day, run the copy from source to target for that day # Use AddDays(1) and -lt to ensure we cover any unexpected variance in the hours, minutes and seconds for the Next Process date while ($nextProcess -lt $today.AddDays(1)) { Write-Verbose ("Processing for date: {0}" -f $nextProcess.ToString("yyyy/MM/dd HH:mm")) # For any source list that has a pattern match for the processing date, copy the relevant cards to the relevant target lists in the target board $templateLists | Select-TrelloListMatches -Date $nextProcess | ForEach-Object { $list = $_.list $listDesc = $_.Name Write-Verbose ("Processing for template list: {0}" -f $list.name) $templateCards | Where-Object { $_.idList -eq $list.id } | ForEach-Object { $card = $_ Write-Verbose ("Processing for card name: {0}" -f $card.name) # Extract the card name components try { $components = Format-TrelloCardName -CardName $card.name } catch { Write-Warning "Failed to parse card name: $_" return } # Ensure the target list exists Write-Verbose ("Card target list: {0}" -f $components.list) $targetList = Get-TrelloLists -Session $session -BoardId $TargetBoardId -FilterFirstNameMatch $components.List if (($targetList | Measure-Object).Count -lt 1) { Write-Verbose "Target list does not exist. Creating." $targetList = Add-TrelloList -Session $session -BoardId $TargetBoardId -Name $components.List } # Copy the card to the target list Write-Verbose "Copying card to target list" $dueDate = $nextProcess.AddDays($components.Grace) $body = [PSCustomObject]@{ name = [string]::Format("{0}/{1}: {2}", $nextProcess.ToString("yyyyMMdd"), $listDesc, $components.Name) keepFromSource = "all" idCardSource = $card.id idList = $targetList.id due = [DateTime]::new($dueDate.Year, $dueDate.Month, $dueDate.Day, 15, 0, 0, [System.DateTimeKind]::Local) } | ConvertTo-Json Write-Verbose "Posting card with body: $body" Invoke-TrelloApi -Session $session -Endpoint "/cards" -Method Post -Body $body | Write-Verbose } } # Increment the processing date Write-Verbose "Incrementing processing date" $nextProcess = $nextProcess.AddDays(1) # Update the 'Last Processed' card Write-Verbose "Updating last processed card" $body = [PSCustomObject]@{ name = "Last Processed: {0}" -f ($now.ToString("yyyy/MM/dd HH:mm")) desc = "Next Process: {0}" -f ($nextProcess.ToString("o")) } | ConvertTo-Json Invoke-TrelloApi -Session $session -Endpoint ("/cards/{0}" -f $lastProcessed.id) -Method Put -Body $body | Write-Verbose } } } <# #> Function Update-TrelloTasksFromConfig { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ConfigPath, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SessionPath ) # Read configuration from file $entries = $null try { $entries = Get-Content -Encoding UTF8 $ConfigPath | ConvertFrom-Json -Depth 3 $entries | ForEach-Object { Test-TrelloValidConfiguration -Config $_ } } catch { Write-Information "Failed to import configuration: $_" throw $_ } # Import Trello session configuration $session = $null try { $session = Import-TrelloSession -Path $SessionPath } catch { Write-Information "Failed to import session: $_" throw $_ } # Iterate through each configuration entry $errors = 0 $entries | ForEach-Object { try { Write-Information ("Running Configuration: " + $_.Name) Write-Information ("Target Board: " + $_.TargetBoardId) Write-Information ("Template Board: " + $_.TemplateBoardId) Update-TrelloTasksFromTemplate -Session $session -Name $_.Name -TemplateBoardId $_.TemplateBoardId -TargetBoardId $_.TargetBoardId } catch { Write-Warning "Failed to process board: $_" $errors++ } } # Exit with error if any of the runs failed Write-Information "Completed processing. Errors: $errors" if ($errors -gt 0) { Write-Error "Errors during processing. May have partially processed." } } <# #> Function Invoke-TrelloTaskUpdateService { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$ConfigPath, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]$SessionPath, [Parameter(Mandatory=$false)] [ValidateNotNull()] [int]$Iterations = 1, [Parameter(Mandatory=$false)] [ValidateSet("Start", "Finish")] [string]$WaitFrom = "Start", [Parameter(Mandatory=$false)] [ValidateNotNull()] [int]$WaitSeconds = 0, [Parameter(Mandatory=$false)] [ValidateNotNull()] [string]$LogPath = "", [Parameter(Mandatory=$false)] [int]$RotateSizeKB = 128, [Parameter(Mandatory=$false)] [int]$PreserveCount = 5 ) process { # Service command block $block = { Update-TrelloTasksFromConfig -ConfigPath $ConfigPath -SessionPath $SessionPath } # Build service parameters $serviceParams = @{ ScriptBlock = $block Iterations = $Iterations WaitFrom = $WaitFrom WaitSeconds = $WaitSeconds LogPath = $LogPath RotateSizeKB = $RotateSizeKB PreserveCount = $PreserveCount } # Actual service invocation Invoke-ServiceRun @serviceParams } } |