diff --git a/PSWorkItem.psd1 b/PSWorkItem.psd1
index b9abe5f..6f218ef 100644
--- a/PSWorkItem.psd1
+++ b/PSWorkItem.psd1
@@ -4,7 +4,7 @@
@{
RootModule = 'PSWorkItem.psm1'
- ModuleVersion = '1.9.0'
+ ModuleVersion = '1.10.0'
CompatiblePSEditions = 'Core'
GUID = '4d3ff215-69ea-4fe6-8ad6-97ffc3a15bfb'
Author = 'Jeff Hicks'
diff --git a/PSWorkItem.psm1 b/PSWorkItem.psm1
index 2a09f1d..b02560d 100644
--- a/PSWorkItem.psm1
+++ b/PSWorkItem.psm1
@@ -1,7 +1,7 @@
# used for culture debugging
# write-host "Importing with culture $(Get-Culture)"
-if ((Get-Culture).Name -match "\w+") {
+if ((Get-Culture).Name -match '\w+') {
Import-LocalizedData -BindingVariable strings
}
else {
@@ -10,7 +10,7 @@ else {
}
#Adding a failsafe check for Windows PowerShell
-if ($IsMacOS -or ($PSEdition -eq "Desktop")) {
+if ($IsMacOS -or ($PSEdition -eq 'Desktop')) {
Write-Warning $Strings.Unsupported
#bail out
Return
@@ -29,12 +29,12 @@ ForEach-Object {
#there may be version conflicts
#required versions
-[version]$NStackVersion = "1.0.7"
-[version]$TerminalGuiVersion = "1.14.0"
+[version]$NStackVersion = '1.1.1.0'
+[version]$TerminalGuiVersion = '1.16.0'
-$dlls = "$PSScriptRoot\assemblies\NStack.dll","$PSScriptRoot\assemblies\Terminal.Gui.dll"
+$dlls = "$PSScriptRoot\assemblies\NStack.dll", "$PSScriptRoot\assemblies\Terminal.Gui.dll"
foreach ($dll in $dlls) {
- $name = Split-Path -path $dll -leaf
+ $name = Split-Path -Path $dll -Leaf
#write-host "Loading $dll" -fore yellow
Try {
Add-Type -Path $dll -ErrorAction Stop
@@ -45,17 +45,17 @@ foreach ($dll in $dlls) {
#$verMessage = "Detected version $($PSStyle.Foreground.Red){0}$($PSStyle.Reset) which is less than the expected version of $($PSStyle.Foreground.Green){1}$($PSStyle.Reset). $($PSStyle.Italic)There may be unexpected behavior$($PSStyle.Reset). You may need to start a new PowerShell session and load this module first."
Switch ($Name) {
- "NStack.dll" {
+ 'NStack.dll' {
#get currently loaded version
$ver = [System.Reflection.Assembly]::GetAssembly([NStack.uString]).GetName().version
if ($ver -lt $NStackVersion) {
- $Detail = $strings.WarnDetected -f $ver,$NStackVersion,$PSStyle.Foreground.Red,$PSStyle.Foreground.Green,$PSStyle.Italic,$PSStyle.Reset
+ $Detail = $strings.WarnDetected -f $ver, $NStackVersion, $PSStyle.Foreground.Red, $PSStyle.Foreground.Green, $PSStyle.Italic, $PSStyle.Reset
}
}
- "Terminal.Gui.dll" {
+ 'Terminal.Gui.dll' {
$ver = [System.Reflection.Assembly]::GetAssembly([Terminal.Gui.Application]).GetName().version
if ($ver -lt $TerminalGuiVersion) {
- $Detail = $strings.WarnDetected -f $ver,$TerminalGuiVersion,$PSStyle.Foreground.Red,$PSStyle.Foreground.Green,$PSStyle.Italic,$PSStyle.Reset
+ $Detail = $strings.WarnDetected -f $ver, $TerminalGuiVersion, $PSStyle.Foreground.Red, $PSStyle.Foreground.Green, $PSStyle.Italic, $PSStyle.Reset
}
}
} #switch
@@ -133,7 +133,7 @@ class PSWorkItemDatabase {
#region type extensions
Update-TypeData -TypeName PSWorkItemCategory -MemberType ScriptProperty -MemberName ANSIString -Value { $PSWorkItemCategory[$this.Category] -replace "`e",
-"``e" } -force
+ "``e" } -Force
#endregion
#region settings and configuration
@@ -160,7 +160,7 @@ If (Test-Path $PreferencePath) {
$global:PSWorkItemDefaultDays = $importPref.DefaultDays
}
If ($importPref.DefaultCategory) {
- $global:PSDefaultParameterValues["New-PSWorkItem:Category"] = $importPref.DefaultCategory
+ $global:PSDefaultParameterValues['New-PSWorkItem:Category'] = $importPref.DefaultCategory
}
}
else {
@@ -169,4 +169,18 @@ else {
$global:PSWorkItemDefaultDays = 30
}
-#endregion
\ No newline at end of file
+#endregion
+
+#region auto completers
+
+Register-ArgumentCompleter -CommandName Set-PSWorkItem,Remove-PSWorkItem,Complete-PSWorkItem -ParameterName ID -ScriptBlock {
+ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
+
+ Invoke-MySQLiteQuery "Select Name,ID,Completed from Tasks" -database $PSWorkItemPath |
+ ForEach-Object {
+ [System.Management.Automation.CompletionResult]::new([int]$_.ID, [int]$_.ID, 'ParameterValue', $_.Name)
+ }
+}
+
+#endregion
+
diff --git a/README.md b/README.md
index fc2377a..59f5298 100644
--- a/README.md
+++ b/README.md
@@ -16,14 +16,6 @@ Install-Module PSWorkItem [-scope CurrentUser]
:heavy_exclamation_mark: Module installation will also install the required [MySQLite](https://github.com/jdhitsolutions/MySQLite) module from the PowerShell Gallery. Linux support was added in `MySQLite v0.13.0`.
-## PSWorkItem Database Change
-
-**If you were using a version of this module older than v1.0.0, this note applies to you.**
-
->Version 1.0.0 of the PSWorkItem module introduced a structural change to the database tables. If you are using a database created in an earlier version, you need to run [Update-PSWorkItemDatabase](docs/Update-PSWorkItemDatabase.md) before adding, changing, or completing work items. You should back up your database file before running this command.
-> Alternatively, you could export your work items, delete the database file, initialize a new one, and re-import your work items.
->During the upgrade, a new table column called ID is added to the Tasks and Archive database tables. In the Tasks table, the ID column for existing entries will be set to the row id, which should be the task number you are used to seeing. In the archive table, existing entries will get an ID value of 0 since knowing the original ID number is impossible. This database change corrects this problem. Going forward, the PSWorkItem ID will remain the same when you complete it and move the item to the Archive table.
-
## Module Commands and Design
- [Add-PSWorkItemCategory](docs/Add-PSWorkItemCategory.md)
@@ -211,7 +203,7 @@ The primary command in this module, `Get-PSWorkItem`, which has an alias of `gwi
The default behavior is to get tasks due within the next ten days
-![Get-PSWorkItem](images/get-PSWorkItem.png)
+![Get-PSWorkItem](images/get-psworkitem.png)
If you are running the command in the PowerShell console or VSCode, overdue tasks will be highlighted in red. Tasks due within three days will be highlighted in yellow.
@@ -238,7 +230,7 @@ The entry will have no effect unless the category is defined in the database. Th
> Note that when you view the hashtable, you won't see any values because the escape sequences are non-printable.
-![colorized categories](images/PSWorkItemcategory.png)
+![colorized categories](images/psworkitemcategory.png)
Category highlighting is only available in the default view.
@@ -388,6 +380,328 @@ A sample database has been created in the module's Samples directory. You can sp
If you copy the sample to `$PSWorkItemPath`, delete the file before creating your database file.
+## Reminders and Alerts TODO
+
+I have received requests and questions about integrating a reminder or alert system. This module does not have any built-in features for this. You could use a scheduled task or a PowerShell script to query the database for tasks due within a certain time frame and then send an email or other alert. I am reluctant to include this feature because I have no way of knowing how you would want to be alerted or what kind of alert you would want. That said, here are some ways you could implement this feature.
+
+### Send-MailKitMessage
+
+I've started using [Send-MailKitMessage](https://github.com/austineric/Send-MailKitMessage) as a replacement for `Send-MailMessage` I use a script to send myself a daily email.
+
+```powershell
+#requires -version 7.2
+#requires -module PSWorkItem,Send-MailKitMessage
+
+Param([int]$Days = 5, [switch]$AsText)
+
+#parameters to splat to Send-MailKitMessage
+$hash = @{
+ Credential = $global:MailCredential
+ From = 'jhicks@jdhitsolutions.com'
+ RecipientList = 'jhicks@jdhitsolutions.com'
+ SMTPServer = $global:SMTPServer
+ Port = $global:SMTPPort
+ Subject = "PSWorkItems Due in the Next $days Days"
+ ErrorAction = 'Stop'
+}
+Write-Host "[$((Get-Date).ToString())] Getting tasks for the next $days days." -ForegroundColor Green
+$data = Get-PSWorkItem -DaysDue $Days
+if ($data) {
+
+ if ($AsText) {
+ Write-Host "[$((Get-Date).ToString())] Sending as TEXT" -ForegroundColor Green
+ # 10/14/2020 Modified to explicitly select properties because
+ # default formatting uses ANSI which distorts the converted output.
+ $body = $data | Select-Object -Property ID, Name, Description, DueDate, OverDue | Format-Table | Out-String
+ $hash.Add('TextBody', $body)
+ }
+
+ else {
+ Write-Host "[$((Get-Date).ToString())] Sending as HTML" -ForegroundColor green
+ #css to be embedded in the html document
+ $head = @"
+
PSWorkItems Due in $Days Days
+
+
+ PSWorkItems
+"@
+ [xml]$html = $data |
+ Select-Object ID, Name, Description, DueDate, Category, Progress, TimeRemaining |
+ ConvertTo-Html -Fragment
+
+ #parse html to add color attributes
+ for ($i = 1; $i -le $html.table.tr.count - 1; $i++) {
+ $class = $html.CreateAttribute('class')
+ #check the number of days until the task is due
+ $due = $html.table.tr[$i].td[-1] -as [TimeSpan]
+ if ($due.Days -le 1) {
+ $class.value = 'alert'
+ $html.table.tr[$i].Attributes.Append($class) | Out-Null
+ }
+ elseif ($due.Days -le 2) {
+ $class.value = 'warn'
+ $html.table.tr[$i].Attributes.Append($class) | Out-Null
+ }
+ }
+
+ $Body = ConvertTo-Html -Body $html.InnerXml -Head $head | Out-String
+ $hash.Add('HTMLBody', $body)
+ }
+}
+else {
+ Write-Warning "No tasks found due in the next $days days."
+ #bail out
+ return
+}
+
+Try {
+ Send-MailKitMessage @hash
+ Write-Output "[$((Get-Date).ToString())] Message ($($hash.subject)) sent to $($hash.RecipientList) from $($hash.from)"
+}
+Catch {
+ throw $_
+}
+```
+
+Because the PSWorkItem module requires PowerShell 7 and PowerShell 7 doesn't support scheduled jobs, I set up a scheduled task to run this script daily.
+
+```powershell
+$action = New-ScheduledTaskAction -Execute 'pwsh.exe' -argument '-nologo -noprofile -file C:\scripts\DailyPSWorkItemEmail.ps1'
+$trigger = New-ScheduledTaskTrigger -Daily -At 7:30AM
+$options = new-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable
+
+$paramHash = @{
+ Force = $True
+ User = "Jeff"
+ Password = Read-Host "Enter the user password"
+ RunLevel = "highest"
+ TaskName = "DailyWorkItem"
+ TaskPath = "\Microsoft\Windows\PowerShellCore\ScheduledJobs\"
+ Description = "Send PSWorkItem email"
+ Settings = $options
+ Trigger = $trigger
+ Action = $action
+}
+
+Register-ScheduledTask @paramHash
+```
+
+I get an HTML-formatted email with tasks due in the next 5 days.
+
+### Toast Notifications
+
+Another option would be to create something with the [BurntToast](https://github.com/Windos/BurntToast) module.
+
+### New-PwshToastAlarm
+
+I use a function in PowerShell 7 to create a toast notification using a PowerShell scheduled job in Windows Powershell.
+
+```powershell
+Function New-PwshToastAlarm {
+
+ <# PSFunctionInfo
+
+Version 1.2.0
+Author Jeffery Hicks
+CompanyName JDH IT Solutions, Inc.
+Copyright (c) JDH IT Solutions, Inc.
+Description Set a toast alarm from PowerShell 7
+Guid 24250c28-5abc-4067-9890-9722482c1a2d
+Tags profile,pwsh
+LastUpdate 9/22/2022 10:15AM
+Source C:\scripts\New-PwshToastAlarm.ps1
+
+#>
+ [cmdletbinding(DefaultParameterSetName = "sound", SupportsShouldProcess)]
+ [alias("nta")]
+ [OutputType("PSScheduledJob")]
+ Param(
+ [Parameter(
+ Position = 0,
+ Mandatory,
+ ValueFromPipelineByPropertyName,
+ HelpMessage = "What date and time do you want to use for the reminder?"
+ )]
+ [ValidateNotNullOrEmpty()]
+ [Alias("Date", "Time")]
+ [DateTime]$At,
+
+ [Parameter(
+ Mandatory,
+ ValueFromPipelineByPropertyName,
+ HelpMessage = "What message do you want to display?"
+ )]
+ [Alias("Event", "Message")]
+ [ValidateNotNullOrEmpty()]
+ [string]$Text,
+
+ [Parameter(HelpMessage = "Specify the path to an image file to use as logo")]
+ [alias("logo")]
+ [string]$AppLogo = "$env:OneDriveConsumer\pictures\psrobot-icon.png",
+
+ [Parameter(HelpMessage = "What sound would you like?", ParameterSetName = "sound")]
+ [ValidateSet('Default', 'IM', 'Mail', 'Reminder', 'SMS', 'Alarm', 'Alarm2', 'Alarm3', 'Alarm4',
+ 'Alarm5', 'Alarm6', 'Alarm7', 'Alarm8', 'Alarm9', 'Alarm10', 'Call', 'Call2', 'Call3', 'Call4', 'Call5',
+ 'Call6', 'Call7', 'Call8', 'Call9', 'Call10', 'None')]
+ [string]$Sound = "Default",
+
+ [Parameter(HelpMessage = "Create a silent alert", ParameterSetName = "silent")]
+ [switch]$Silent,
+
+ [Parameter(HelpMessage = "Specify a job name")]
+ [string]$Name = "BTReminder-$(Get-Random -Minimum 1000 -Maximum 9999)"
+ )
+
+ Begin {
+ Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN ] Starting $($MyInvocation.MyCommand)"
+ } #begin
+ Process {
+ Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Defining a toast job for $At - $Text"
+ $h = @{
+ Text = $Text
+ At = $At
+ Sound = $sound
+ Name = $Name
+ }
+
+ if ($AppLogo) {
+ $h.add("AppLogo", $AppLogo)
+ }
+ $cmd = [System.Collections.Generic.list[string]]::new()
+ $alarms = [System.Collections.Generic.list[string]]::new()
+
+ $cmd.add("&{ . c:\scripts\New-ToastAlarm.ps1 ; ")
+ $alarms.Add("New-ToastAlarm ")
+
+ $h.GetEnumerator() | ForEach-Object {
+ $alarms.Add("-$($_.key) '$($_.value)' ")
+ }
+
+ $a = $alarms -join ''
+ $cmd.Add("$a}")
+
+ Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Invoking: $cmd"
+ if ($PSCmdlet.ShouldProcess($a)) {
+ powershell -NoLogo -NoProfile -command $cmd
+ }
+ }
+ End {
+ Write-Verbose "[$((Get-Date).TimeOfDay) END ] Ending $($MyInvocation.MyCommand)"
+ } #end
+
+} #close function
+```
+
+This function calls another script that creates the toast notification as a scheduled job.
+
+```powershell
+#requires -version 5.1
+#requires -module BurntToast,PSScheduledJob
+
+#New-ToastAlarm.ps1
+
+Function New-BTReminder {
+ [cmdletbinding(DefaultParameterSetName = 'sound', SupportsShouldProcess)]
+ [alias('nta', 'New-ToastAlarm')]
+ param(
+ [Parameter(Position = 0, Mandatory, HelpMessage = 'What date and time do you want to use for the reminder?')]
+ [ValidateNotNullOrEmpty()]
+ [DateTime]$At,
+ [Parameter(Mandatory, HelpMessage = 'What message do you want to display?')]
+ [ValidateNotNullOrEmpty()]
+ [string]$Text,
+ [Parameter(HelpMessage = 'Specify the path to an image file to use as logo')]
+ [alias('Logo')]
+ [string]$AppLogo,
+ [Parameter(HelpMessage = 'What sound would you like?', ParameterSetName = 'sound')]
+ [ValidateSet('Default', 'IM', 'Mail', 'Reminder', 'SMS', 'Alarm', 'Alarm2', 'Alarm3', 'Alarm4',
+ 'Alarm5', 'Alarm6', 'Alarm7', 'Alarm8', 'Alarm9', 'Alarm10', 'Call', 'Call2', 'Call3', 'Call4', 'Call5',
+ 'Call6', 'Call7', 'Call8', 'Call9', 'Call10', 'None')]
+ [string]$Sound,
+ [Parameter(HelpMessage = 'Create a silent alert', ParameterSetName = 'silent')]
+ [switch]$Silent,
+ [Parameter(HelpMessage = 'Specify a job name')]
+ [string]$Name = "BTReminder-$(Get-Random -Minimum 1000 -Maximum 9999))"
+ )
+
+ Write-Verbose "Starting $($MyInvocation.MyCommand)"
+ Write-Verbose "Running in PowerShell $($PSVersionTable.PSVersion)"
+ $PSBoundParameters | Out-String | Write-Verbose
+
+ [void]($PSBoundParameters.remove('At'))
+ [void]($PSBoundParameters.remove('Name'))
+
+ $toastParams = @{}
+
+ $PSBoundParameters.GetEnumerator() | ForEach-Object {
+ Write-Verbose "Adding $($_.key) to `$toastParams"
+ $toastParams.Add($_.key, $_.value)
+ }
+ #add a toast expiration
+ $toastParams.Add('Expiration', $At.AddMinutes(5))
+ $toastParams.Add('SnoozeAndDismiss', $True)
+ $sb = {
+ param([hashtable]$Params, [string]$JobName)
+ #The Write-Host output will only show if you receive the job. Use for troubleshooting.
+ Write-Host 'Defining a toast job using these params' -ForegroundColor Green
+ $params | Out-String | Write-Host
+ Write-Host 'Toasting...' -ForegroundColor Green
+ New-BurntToastNotification @params
+ Write-Host Sleeping -ForegroundColor Green
+ Start-Sleep -Seconds 60
+ Write-Host "attempting to unregister $jobName" -ForegroundColor Green
+ Unregister-ScheduledJob -Name $JobName
+ }
+
+ $job = @{
+ Trigger = New-JobTrigger -At $At -Once
+ Name = $Name
+ MaxResultCount = 1
+ ScriptBlock = $sb
+ ArgumentList = @($toastParams, $Name)
+ }
+
+ Write-Verbose 'Using toast params'
+ $toastParams | Out-String | Write-Verbose
+
+ Write-Verbose 'Registering job'
+ $job | Out-String | Write-Verbose
+
+ Register-ScheduledJob @job
+
+ Write-Verbose "Ending $($MyInvocation.MyCommand)"
+}
+```
+Now it is a matter of deciding when you want to be notified.
+
+```powershell
+Get-PSWorkItem | where {-Not $_.OverDue} |
+Foreach-Object -Begin { . C:\scripts\New-PwshToastAlarm.ps1} -process {New-PwshToastAlarm -At ([datetime]$_.dueDate).addDays(-1) -Text "Workitem $($_.Name) is due $($_.DueDate)"}
+```
+
+This is why I am hesitant to add any form of alerting or notification. I don't know what is the most effective way for **you** to be alerted. And, any alerting feature I add would more than likely add a dependency on a specific module which I try to avoid.
+
+## PSWorkItem Database Change
+
+**If you were using a version of this module older than v1.0.0, this note applies to you.**
+
+> *Version 1.0.0 of the PSWorkItem module introduced a structural change to the database tables. If you are using a database created in an earlier version, you need to run [Update-PSWorkItemDatabase](docs/Update-PSWorkItemDatabase.md) before adding, changing, or completing work items. You should back up your database file before running this command.
+> Alternatively, you could export your work items, delete the database file, initialize a new one, and re-import your work items.
+>During the upgrade, a new table column called ID is added to the Tasks and Archive database tables. In the Tasks table, the ID column for existing entries will be set to the row id, which should be the task number you are used to seeing. In the archive table, existing entries will get an ID value of 0 since knowing the original ID number is impossible. This database change corrects this problem. Going forward, the PSWorkItem ID will remain the same when you complete it and move the item to the Archive table.*
+
## Troubleshooting
Most of the commands in this module create custom objects derived from PowerShell [class definitions](PSWorkItem.psm1) and data in the SQLite database file. If you need to troubleshoot a problem, you can use `Get-PSWorkItemData` to select all data from one of the three tables.
diff --git a/assemblies/NStack.dll b/assemblies/NStack.dll
index 0883d81..d3933bb 100644
Binary files a/assemblies/NStack.dll and b/assemblies/NStack.dll differ
diff --git a/assemblies/Terminal.Gui.dll b/assemblies/Terminal.Gui.dll
index 8c89d63..4e56db9 100644
Binary files a/assemblies/Terminal.Gui.dll and b/assemblies/Terminal.Gui.dll differ
diff --git a/changelog.md b/changelog.md
index 25bc691..d2439ec 100644
--- a/changelog.md
+++ b/changelog.md
@@ -2,6 +2,18 @@
## [Unreleased]
+## [1.10.0] - 2024-05-28
+
+### Added
+
+- Added argument completer for the `ID` parameter of `Complete-PSWorkItem`, `Set-PSWorkItem`, and `Remove-PSWorkItem`
+
+### Changed
+
+- Updated `README`
+- Updated `Terminal.gui` assembly to version 1.16.0
+- Updated `Nstack` assembly to version 1.1.1.0
+
## [1.9.0] - 2024-02-24
### Changed
@@ -311,7 +323,8 @@ This is a major update with significant changes. If this is your first time inst
- Initial files
- Created Module outline
-[Unreleased]: https://github.com/jdhitsolutions/PSWorkItem/compare/v1.9.0..HEAD
+[Unreleased]: https://github.com/jdhitsolutions/PSWorkItem/compare/v1.10.0..HEAD
+[1.10.0]: https://github.com/jdhitsolutions/PSWorkItem/compare/v1.9.0..v1.10.0
[1.9.0]: https://github.com/jdhitsolutions/PSWorkItem/compare/v1.8.0..v1.9.0
[1.8.0]: https://github.com/jdhitsolutions/PSWorkItem/compare/v1.7.0..v1.8.0
[1.7.0]: https://github.com/jdhitsolutions/PSWorkItem/compare/v1.5.0..v1.7.0
diff --git a/functions/public/New-PSWorkItem.ps1 b/functions/public/New-PSWorkItem.ps1
index a9c1909..80d46a8 100644
--- a/functions/public/New-PSWorkItem.ps1
+++ b/functions/public/New-PSWorkItem.ps1
@@ -156,7 +156,7 @@ Function New-PSWorkItem {
$task | Select-Object * | Out-String | Write-Debug
<#
6 Aug 2022 variable expansion appears to be culture-invariant. This is a problem
- with datetime values. Explicitly getting a string appears to resolve problem. - JDH
+ with DateTime values. Explicitly getting a string appears to resolve problem. - JDH
#>
_verbose -message ($strings.TaskCreated -f $task.taskcreated.ToString())