diff --git a/Examples/AsyncNotification.ps1 b/Examples/AsyncNotification.ps1
index 2d82f64..a0a4764 100644
--- a/Examples/AsyncNotification.ps1
+++ b/Examples/AsyncNotification.ps1
@@ -1,84 +1,60 @@
# Example: Asynchronous Notification
-# This script demonstrates how to show an asynchronous notification and check its status later
+# This script demonstrates how to show an asynchronous notification
-# Load the WindowsNotifications assembly
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
+
+# Initialize the notification system
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
+Initialize-WindowsNotifications -DllPath $dllPath
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
+# Show an asynchronous notification
+$result = Show-Notification -Title "Background Task" -Message "A background task is running..." -Buttons "Cancel", "View Details" -Async
-# Create custom notification options
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "Background Task Running"
-$options.Message = "A background task is running. You can continue working."
-$options.Async = $true # Run asynchronously
-$options.PersistState = $true # Save state to database
-
-# Add buttons
-$viewButton = New-Object WindowsNotifications.Models.NotificationButton("View Progress", "view")
-$cancelButton = New-Object WindowsNotifications.Models.NotificationButton("Cancel Task", "cancel")
-$options.Buttons.Add($viewButton)
-$options.Buttons.Add($cancelButton)
-
-# Show the notification
-Write-Host "Showing asynchronous notification..."
-$result = $notificationManager.ShowNotification($options)
-
-# Display the result
+# Display the initial result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# Simulate doing some work
-Write-Host "Performing background work..."
+# Simulate a background task
+Write-Host "Performing background task..."
for ($i = 1; $i -le 5; $i++) {
- Write-Host " Working... ($i/5)"
+ Write-Host "Working... ($i/5)"
Start-Sleep -Seconds 2
# Check if the notification has been interacted with
- $currentResult = $notificationManager.GetNotificationResult($result.NotificationId)
+ $currentResult = Get-NotificationResult -NotificationId $result.NotificationId
- if ($currentResult -ne $null -and ($currentResult.Activated -or $currentResult.Dismissed)) {
- if ($currentResult.ClickedButtonId -eq "cancel") {
- Write-Host "User canceled the task!"
- break
- }
- elseif ($currentResult.ClickedButtonId -eq "view") {
- Write-Host "User viewed the progress!"
+ if ($currentResult -and ($currentResult.Activated -or $currentResult.Dismissed)) {
+ Write-Host "User interacted with the notification"
+
+ if ($currentResult.ClickedButtonId) {
+ Write-Host "Button clicked: $($currentResult.ClickedButtonText) (ID: $($currentResult.ClickedButtonId))"
- # Show a new notification with updated progress
- $progressOptions = New-Object WindowsNotifications.Models.NotificationOptions
- $progressOptions.Title = "Task Progress"
- $progressOptions.Message = "Progress: $i/5 steps completed"
- $progressOptions.Tag = "progress" # Group with the same tag
- $notificationManager.ShowNotification($progressOptions)
+ # Handle button clicks
+ if ($currentResult.ClickedButtonText -eq "Cancel") {
+ Write-Host "User cancelled the task"
+ break
+ } elseif ($currentResult.ClickedButtonText -eq "View Details") {
+ Write-Host "User wants to view details"
+ # In a real script, you would show details here
+ }
}
+
+ break
}
}
-Write-Host "Background work completed"
+Write-Host "Background task completed"
-# Show a completion notification
-$completionOptions = New-Object WindowsNotifications.Models.NotificationOptions
-$completionOptions.Title = "Task Completed"
-$completionOptions.Message = "The background task has finished."
-$completionOptions.Tag = "progress" # Replace previous notification with same tag
-$notificationManager.ShowNotification($completionOptions)
-
-# Get the final notification result
-$finalResult = $notificationManager.GetNotificationResult($result.NotificationId)
-if ($finalResult -ne $null) {
+# Get the final result
+$finalResult = Get-NotificationResult -NotificationId $result.NotificationId
+if ($finalResult) {
Write-Host "Final notification state:"
Write-Host " Activated: $($finalResult.Activated)"
Write-Host " Dismissed: $($finalResult.Dismissed)"
+
if ($finalResult.ClickedButtonId) {
- Write-Host " Button clicked: $($finalResult.ClickedButtonText)"
+ Write-Host " Button clicked: $($finalResult.ClickedButtonText) (ID: $($finalResult.ClickedButtonId))"
}
}
diff --git a/Examples/CountdownAndDeadline.ps1 b/Examples/CountdownAndDeadline.ps1
index a9a961b..72e493d 100644
--- a/Examples/CountdownAndDeadline.ps1
+++ b/Examples/CountdownAndDeadline.ps1
@@ -1,125 +1,44 @@
# Example: Countdown and Deadline Notification
-# This script demonstrates how to create a notification with a countdown timer and deadline action
+# This script demonstrates how to show a notification with a countdown timer and deadline
-# Load the WindowsNotifications assembly
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
+
+# Initialize the notification system
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
+Initialize-WindowsNotifications -DllPath $dllPath
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
+# Set a deadline 2 minutes from now
+$deadline = (Get-Date).AddMinutes(2)
-# Create custom notification options
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "System Maintenance Required"
-$options.Message = "Your system needs to restart for maintenance. Please save your work."
-$options.PersistState = $true
-$options.EnableLogging = $true
-
-# Set up logging
-$logFile = Join-Path -Path $PSScriptRoot -ChildPath "notification_log.txt"
-$options.LogAction = {
- param($logEntry)
- Add-Content -Path $logFile -Value $logEntry
- Write-Host $logEntry
-}
-
-# Set deadline (5 minutes from now)
-$options.DeadlineTime = (Get-Date).AddMinutes(5)
-$options.ShowCountdown = $true
-
-# Create deadline action (restart computer)
-$deadlineAction = [WindowsNotifications.Models.DeadlineAction]::ExecuteScript("Write-Host 'System would restart now (simulated)'; Start-Sleep -Seconds 5")
-$options.DeadlineAction = $deadlineAction
-
-# Add buttons
-$restartButton = New-Object WindowsNotifications.Models.NotificationButton("Restart Now", "restart")
-$deferButton = New-Object WindowsNotifications.Models.NotificationButton("Defer", "defer")
-$options.Buttons.Add($restartButton)
-$options.Buttons.Add($deferButton)
-
-# Configure deferral options
-$options.DeferralOptions = New-Object WindowsNotifications.Models.DeferralOptions
-$options.DeferralOptions.DeferButtonText = "Defer Restart"
-$options.DeferralOptions.DeferralPrompt = "Postpone until:"
-$options.DeferralOptions.MaxDeferrals = 2
-$options.DeferralOptions.EnforceMaxDeferrals = $true
-$options.DeferralOptions.ScheduleReminder = $true
-
-# Clear existing deferral choices and add custom ones
-$options.DeferralOptions.DeferralChoices.Clear()
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("1 minute", [TimeSpan]::FromMinutes(1), "1min")))
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("2 minutes", [TimeSpan]::FromMinutes(2), "2min")))
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("3 minutes", [TimeSpan]::FromMinutes(3), "3min")))
-
-# Set up event handlers
-$options.OnActivated = {
- param($result)
- $message = "Notification was activated with button: $($result.ClickedButtonText)"
- Write-Host $message
- Add-Content -Path $logFile -Value $message
-
- if ($result.ClickedButtonId -eq "restart") {
- Write-Host "User chose to restart now (simulated)"
- # In a real script, you would restart the computer here
- # Restart-Computer -Force
- }
-}
-
-$options.OnTimeout = {
- param($result)
- $message = "Notification timed out"
- Write-Host $message
- Add-Content -Path $logFile -Value $message
-}
-
-$options.OnError = {
- param($result)
- $message = "Error occurred: $($result.ErrorMessage)"
- Write-Host $message -ForegroundColor Red
- Add-Content -Path $logFile -Value $message
-}
-
-# Show the notification
-Write-Host "Showing countdown notification with deadline..."
-$result = $notificationManager.ShowNotification($options)
+# Show a countdown notification
+$result = Show-Notification -Title "System Maintenance Required" -Message "Your system needs to restart for maintenance. Please save your work." -DeadlineTime $deadline -ShowCountdown -Buttons "Restart Now", "Remind Me Later"
# Display the result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# Wait for the deadline to pass
-Write-Host "Waiting for user interaction or deadline to pass..."
-$waitTime = [int]([Math]::Ceiling(($options.DeadlineTime - (Get-Date)).TotalSeconds)) + 5
-Start-Sleep -Seconds $waitTime
-
-# Get the final result
-$finalResult = $notificationManager.GetNotificationResult($result.NotificationId)
-if ($finalResult -ne $null) {
- Write-Host "`nFinal notification state:"
- Write-Host " Activated: $($finalResult.Activated)"
- Write-Host " Dismissed: $($finalResult.Dismissed)"
- Write-Host " Deferred: $($finalResult.Deferred)"
-
- if ($finalResult.Deferred) {
- Write-Host " Deferred until: $($finalResult.DeferredUntil)"
- Write-Host " Deferral reason: $($finalResult.DeferralReason)"
- }
-
- if ($finalResult.DeadlineReached) {
- Write-Host " Deadline reached at: $($finalResult.DeadlineReachedTime)"
- Write-Host " Deadline action: $($finalResult.DeadlineAction)"
- }
-
- if ($finalResult.ClickedButtonId) {
- Write-Host " Button clicked: $($finalResult.ClickedButtonText)"
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+ if ($result.ClickedButtonId) {
+ Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
+
+ # Handle button clicks
+ if ($result.ClickedButtonText -eq "Restart Now") {
+ Write-Host "User chose to restart now (simulated)"
+ # In a real script, you would restart the computer here
+ # Restart-Computer -Force
+ } elseif ($result.ClickedButtonText -eq "Remind Me Later") {
+ Write-Host "User chose to be reminded later"
+ # In a real script, you would schedule a reminder here
+ }
}
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
+} elseif ($result.DeadlineReached) {
+ Write-Host "Deadline was reached at $($result.DeadlineReachedTime)"
+ Write-Host "Taking automatic action (simulated)"
+ # In a real script, you would take the automatic action here
}
-
-Write-Host "`nLog file: $logFile"
diff --git a/Examples/CustomBrandedNotification.ps1 b/Examples/CustomBrandedNotification.ps1
index db4a09a..b3e4534 100644
--- a/Examples/CustomBrandedNotification.ps1
+++ b/Examples/CustomBrandedNotification.ps1
@@ -1,105 +1,62 @@
# Example: Custom Branded Notification
-# This script demonstrates how to create a notification with custom branding
+# This script demonstrates how to show a notification with custom branding
-# Load the WindowsNotifications assembly
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
+
+# Initialize the notification system
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
+Initialize-WindowsNotifications -DllPath $dllPath
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Create custom notification options with branding
+# Create custom notification options
$options = New-Object WindowsNotifications.Models.NotificationOptions
-
-# Set basic notification properties
$options.Title = "IT Department Notification"
-$options.Message = "Your system has been selected for a security update. Please review the details below."
+$options.Message = "Your system has been selected for a security update."
# Set branding properties
-$options.BrandingText = "Contoso IT Department"
-$options.BrandingColor = "#0078D7" # Blue
-$options.AccentColor = "#E81123" # Red
-$options.UseDarkTheme = $true
+$options.Attribution = "Contoso IT Department"
-# Set custom images
-# Note: Replace these paths with actual image paths on your system
-$logoPath = Join-Path -Path $PSScriptRoot -ChildPath "Images\company-logo.png"
+# Set custom images (file paths or URLs)
+$logoPath = Join-Path -Path $PSScriptRoot -ChildPath "logo.png"
if (Test-Path $logoPath) {
$options.LogoImagePath = $logoPath
}
-else {
- # Use a URL as fallback
- $options.LogoImagePath = "https://www.contoso.com/images/logo.png"
-}
-$heroPath = Join-Path -Path $PSScriptRoot -ChildPath "Images\banner.png"
+$heroPath = Join-Path -Path $PSScriptRoot -ChildPath "banner.png"
if (Test-Path $heroPath) {
$options.HeroImagePath = $heroPath
}
-# Add custom buttons with images
+# Add custom buttons
$updateButton = New-Object WindowsNotifications.Models.NotificationButton("Install Update", "install")
-$updateButton.BackgroundColor = "#107C10" # Green
-$updateButton.TextColor = "#FFFFFF" # White
-
-$deferButton = New-Object WindowsNotifications.Models.NotificationButton("Defer", "defer")
-$deferButton.BackgroundColor = "#5A5A5A" # Gray
-$deferButton.TextColor = "#FFFFFF" # White
-
-$moreInfoButton = New-Object WindowsNotifications.Models.NotificationButton("More Info", "info")
-$moreInfoButton.IsContextMenu = $true
-
-# Add buttons to the notification
+$laterButton = New-Object WindowsNotifications.Models.NotificationButton("Later", "later")
$options.Buttons.Add($updateButton)
-$options.Buttons.Add($deferButton)
-$options.Buttons.Add($moreInfoButton)
-
-# Configure audio
-$options.AudioSource = "ms-winsoundevent:Notification.Default"
-$options.SilentMode = $false
+$options.Buttons.Add($laterButton)
# Show the notification
-Write-Host "Showing custom branded notification..."
+$notificationManager = New-Object WindowsNotifications.NotificationManager
$result = $notificationManager.ShowNotification($options)
# Display the result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# If the notification was displayed, wait for interaction
-if ($result.Displayed) {
- Write-Host "Waiting for user interaction..."
- $result = $notificationManager.WaitForNotification($result.NotificationId)
-
- if ($result -ne $null) {
- Write-Host "Notification was activated: $($result.Activated)"
- Write-Host "Notification was dismissed: $($result.Dismissed)"
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+ if ($result.ClickedButtonId) {
+ Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
- if ($result.ClickedButtonId) {
- Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
-
- # Take action based on which button was clicked
- switch ($result.ClickedButtonId) {
- "install" {
- Write-Host "User chose to install the update"
- # In a real script, you would initiate the update here
- }
- "defer" {
- Write-Host "User chose to defer the update"
- # In a real script, you would schedule a reminder here
- }
- "info" {
- Write-Host "User requested more information"
- # In a real script, you would open a help page or display more details
- }
- }
+ # Handle button clicks
+ if ($result.ClickedButtonId -eq "install") {
+ Write-Host "User chose to install the update (simulated)"
+ # In a real script, you would install the update here
+ } elseif ($result.ClickedButtonId -eq "later") {
+ Write-Host "User chose to install later"
+ # In a real script, you would schedule a reminder here
}
}
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
}
diff --git a/Examples/LoadFromBase64.ps1 b/Examples/LoadFromBase64.ps1
index be3ad60..170f4b7 100644
--- a/Examples/LoadFromBase64.ps1
+++ b/Examples/LoadFromBase64.ps1
@@ -1,35 +1,29 @@
-# Example: Load Assembly from Base64
+# Example: Load from Base64
# This script demonstrates how to load the WindowsNotifications assembly from a Base64 string
-# This is useful for embedding the assembly directly in a script
-# In a real scenario, you would replace this with the actual Base64 string of the assembly
-# For demonstration purposes, we'll load the DLL and convert it to Base64
+# First, convert the DLL to a Base64 string
+# In a real scenario, you would have this string pre-generated
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $base64 = [Convert]::ToBase64String($bytes)
-
- Write-Host "Assembly loaded and converted to Base64"
- Write-Host "Base64 string length: $($base64.Length) characters"
-
- # In a real script, the base64 string would be hardcoded here
- # $base64 = "YOUR_BASE64_STRING_HERE"
-
- # Load the assembly from the Base64 string
- $assemblyBytes = [Convert]::FromBase64String($base64)
- $assembly = [System.Reflection.Assembly]::Load($assemblyBytes)
-
- Write-Host "Assembly loaded successfully: $($assembly.FullName)"
-
- # Create a notification manager and show a simple notification
- $notificationManager = New-Object WindowsNotifications.NotificationManager
- $result = $notificationManager.ShowSimpleNotification(
- "Loaded from Base64",
- "This notification was shown from an assembly loaded from a Base64 string"
- )
-
- Write-Host "Notification displayed: $($result.Displayed)"
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
+$bytes = [System.IO.File]::ReadAllBytes($dllPath)
+$base64 = [Convert]::ToBase64String($bytes)
+
+# Now, load the assembly from the Base64 string
+$assemblyBytes = [Convert]::FromBase64String($base64)
+$assembly = [System.Reflection.Assembly]::Load($assemblyBytes)
+
+# Create a notification manager
+$notificationManager = New-Object WindowsNotifications.NotificationManager
+
+# Show a simple notification
+$result = $notificationManager.ShowSimpleNotification("Loaded from Base64", "This notification was shown using an assembly loaded from a Base64 string.")
+
+# Display the result
+Write-Host "Notification displayed: $($result.Displayed)"
+Write-Host "Notification ID: $($result.NotificationId)"
+
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
}
diff --git a/Examples/NotificationWithButtons.ps1 b/Examples/NotificationWithButtons.ps1
index ad0ad1f..b0a5bae 100644
--- a/Examples/NotificationWithButtons.ps1
+++ b/Examples/NotificationWithButtons.ps1
@@ -1,58 +1,27 @@
# Example: Notification with Buttons
# This script demonstrates how to show a notification with buttons
-# Load the WindowsNotifications assembly
-$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
+# Initialize the notification system
+$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
+Initialize-WindowsNotifications -DllPath $dllPath
# Show a notification with buttons
-Write-Host "Showing notification with buttons..."
-$result = $notificationManager.ShowNotificationWithButtons(
- "Action Required",
- "Please select an option below:",
- "Approve",
- "Reject",
- "Defer"
-)
+$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer"
# Display the result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# If the notification was displayed, wait for interaction
-if ($result.Displayed) {
- Write-Host "Waiting for user interaction..."
- $result = $notificationManager.WaitForNotification($result.NotificationId)
-
- if ($result -ne $null) {
- Write-Host "Notification was activated: $($result.Activated)"
- Write-Host "Notification was dismissed: $($result.Dismissed)"
-
- if ($result.ClickedButtonId) {
- Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
-
- # Take action based on which button was clicked
- switch ($result.ClickedButtonText) {
- "Approve" {
- Write-Host "User approved the action"
- }
- "Reject" {
- Write-Host "User rejected the action"
- }
- "Defer" {
- Write-Host "User deferred the action"
- }
- }
- }
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+ if ($result.ClickedButtonId) {
+ Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
}
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
}
diff --git a/Examples/PowerShellModule.ps1 b/Examples/PowerShellModule.ps1
index 9a3959c..354c1c1 100644
--- a/Examples/PowerShellModule.ps1
+++ b/Examples/PowerShellModule.ps1
@@ -1,21 +1,13 @@
-# Example: Using the WindowsNotifications PowerShell Module
-# This script demonstrates how to use the WindowsNotifications PowerShell module
+# Example: PowerShell Module
+# This script demonstrates how to use the PowerShell module
# Import the module
-$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell"
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
Import-Module $modulePath -Force
-# Initialize the module
-# Note: The DLL must be in the same directory as the module files
+# Initialize the notification system
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- Write-Host "Initializing WindowsNotifications module with DLL at: $dllPath"
- Initialize-WindowsNotifications -AssemblyPath $dllPath
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
+Initialize-WindowsNotifications -DllPath $dllPath
# Check if running as SYSTEM
$isSystem = Test-SystemContext
@@ -23,94 +15,29 @@ Write-Host "Running as SYSTEM: $isSystem"
# Get interactive user sessions
$sessions = Get-InteractiveUserSessions
-Write-Host "Interactive user sessions found: $($sessions.Count)"
+Write-Host "Interactive user sessions: $($sessions.Count)"
foreach ($session in $sessions) {
- Write-Host " $session"
+ Write-Host " - $session"
}
# Show a simple notification
-Write-Host "`nShowing simple notification..."
-$result = Show-Notification -Title "Simple Notification" -Message "This is a simple notification from the PowerShell module"
+$result = Show-Notification -Title "PowerShell Module" -Message "This notification was shown using the PowerShell module."
# Display the result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# Show a notification with buttons
-Write-Host "`nShowing notification with buttons..."
-$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer"
-
-# Display the result
-Write-Host "Notification displayed: $($result.Displayed)"
-Write-Host "Notification ID: $($result.NotificationId)"
-
-if ($result.ClickedButtonId) {
- Write-Host "Button clicked: $($result.ClickedButtonText)"
-
- switch ($result.ClickedButtonText) {
- "Approve" { Write-Host "User approved the action" }
- "Reject" { Write-Host "User rejected the action" }
- "Defer" { Write-Host "User deferred the action" }
- }
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
}
-# Show a reboot notification
-Write-Host "`nShowing reboot notification..."
-$result = Show-Notification -Title "System Reboot Required" -Message "Your system needs to be rebooted to complete updates." -RebootButtonText "Reboot Now" -DeferButtonText "Defer"
+# Get all notification results
+$results = Get-AllNotificationResults
+Write-Host "Total notification results: $($results.Count)"
-# Display the result
-Write-Host "Notification displayed: $($result.Displayed)"
-Write-Host "Notification ID: $($result.NotificationId)"
-
-if ($result.ClickedButtonId -eq "reboot") {
- Write-Host "User chose to reboot now"
- # In a real script, you would initiate a reboot here
- # Restart-Computer -Force
-}
-elseif ($result.Deferred) {
- Write-Host "User deferred the reboot until: $($result.DeferredUntil)"
- Write-Host "Deferral reason: $($result.DeferralReason)"
-}
-
-# Show an asynchronous notification
-Write-Host "`nShowing asynchronous notification..."
-$result = Show-Notification -Title "Background Task" -Message "A background task is running" -Async -PersistState
-
-# Display the result
-Write-Host "Notification displayed: $($result.Displayed)"
-Write-Host "Notification ID: $($result.NotificationId)"
-
-# Simulate doing some work
-Write-Host "Performing background work..."
-for ($i = 1; $i -le 5; $i++) {
- Write-Host " Working... ($i/5)"
- Start-Sleep -Seconds 2
-
- # Check if the notification has been interacted with
- $currentResult = Get-NotificationResult -NotificationId $result.NotificationId
-
- if ($currentResult -ne $null -and ($currentResult.Activated -or $currentResult.Dismissed)) {
- if ($currentResult.ClickedButtonId) {
- Write-Host "User clicked: $($currentResult.ClickedButtonText)"
- }
- else {
- Write-Host "User interacted with the notification"
- }
- }
-}
-
-Write-Host "Background work completed"
-
-# Get notification history
-Write-Host "`nGetting notification history..."
-$history = Get-NotificationHistory
-Write-Host "Found $($history.Count) notifications in history"
-
-foreach ($item in $history) {
- Write-Host " ID: $($item.NotificationId), Created: $($item.CreatedTime), Activated: $($item.Activated), Dismissed: $($item.Dismissed)"
-}
-
-# Clean up notification history
-Write-Host "`nCleaning up notification history..."
-$result = Remove-NotificationHistory
-Write-Host "Notification history cleared: $result"
+# Clear all notification results
+Clear-AllNotificationResults
+Write-Host "All notification results cleared"
diff --git a/Examples/PowerShellModuleNew.ps1 b/Examples/PowerShellModuleNew.ps1
new file mode 100644
index 0000000..3c6ff77
--- /dev/null
+++ b/Examples/PowerShellModuleNew.ps1
@@ -0,0 +1,42 @@
+# Example: PowerShell Module
+# This script demonstrates how to use the PowerShell module
+
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
+
+# Initialize the notification system
+Initialize-WindowsNotifications
+
+# Check if running as SYSTEM
+$isSystem = Test-SystemContext
+Write-Output "Running as SYSTEM: $isSystem"
+
+# Get interactive user sessions
+$sessions = Get-InteractiveUserSessions
+Write-Output "Interactive user sessions: $($sessions.Count)"
+foreach ($session in $sessions) {
+ Write-Output " - $session"
+}
+
+# Show a simple notification
+$result = Show-Notification -Title "PowerShell Module" -Message "This notification was shown using the PowerShell module."
+
+# Display the result
+Write-Output "Notification displayed: $($result.Displayed)"
+Write-Output "Notification ID: $($result.NotificationId)"
+
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Output "Notification was activated"
+} elseif ($result.Dismissed) {
+ Write-Output "Notification was dismissed"
+}
+
+# Get all notification results
+$results = Get-AllNotificationResults
+Write-Output "Total notification results: $($results.Count)"
+
+# Clear all notification results
+Clear-AllNotificationResults
+Write-Output "All notification results cleared"
diff --git a/Examples/README.md b/Examples/README.md
new file mode 100644
index 0000000..227d941
--- /dev/null
+++ b/Examples/README.md
@@ -0,0 +1,10 @@
+# Examples
+
+This directory contains example scripts demonstrating how to use the Windows Notifications system.
+
+## Examples
+
+- `SimpleNotification.ps1` - Shows a simple notification
+- `NotificationWithButtons.ps1` - Shows a notification with buttons
+- `CountdownNotification.ps1` - Shows a notification with a countdown timer
+- `SystemContextNotification.ps1` - Shows a notification from SYSTEM context
diff --git a/Examples/RebootNotification.ps1 b/Examples/RebootNotification.ps1
index af81cc9..1ec624d 100644
--- a/Examples/RebootNotification.ps1
+++ b/Examples/RebootNotification.ps1
@@ -1,68 +1,36 @@
-# Example: Reboot Notification with Deferrals
+# Example: Reboot Notification
# This script demonstrates how to show a reboot notification with deferral options
-# Load the WindowsNotifications assembly
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
+
+# Initialize the notification system
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
+Initialize-WindowsNotifications -DllPath $dllPath
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Create custom notification options for a reboot
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "System Reboot Required"
-$options.Message = "Your system needs to be rebooted to complete important updates. Please save your work."
-$options.PersistState = $true
-
-# Add reboot button
-$rebootButton = New-Object WindowsNotifications.Models.NotificationButton("Reboot Now", "reboot")
-$options.Buttons.Add($rebootButton)
-
-# Configure deferral options
-$options.DeferralOptions = New-Object WindowsNotifications.Models.DeferralOptions
-$options.DeferralOptions.DeferButtonText = "Defer Reboot"
-$options.DeferralOptions.DeferralPrompt = "Postpone until:"
-$options.DeferralOptions.MaxDeferrals = 3
-
-# Clear existing deferral choices and add custom ones
-$options.DeferralOptions.DeferralChoices.Clear()
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("1 hour", [TimeSpan]::FromHours(1), "1hour")))
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("4 hours", [TimeSpan]::FromHours(4), "4hours")))
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("End of day", [TimeSpan]::FromHours(8), "endofday")))
-$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("Tomorrow", [TimeSpan]::FromDays(1), "tomorrow")))
-
-# Show the notification
-Write-Host "Showing reboot notification..."
-$result = $notificationManager.ShowNotification($options)
+# Show a reboot notification
+$result = Show-Notification -Title "System Reboot Required" -Message "Your system needs to be rebooted to complete updates." -RebootButtonText "Reboot Now" -DeferButtonText "Defer"
# Display the result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# If the notification was displayed, wait for interaction
-if ($result.Displayed) {
- Write-Host "Waiting for user interaction..."
- $result = $notificationManager.WaitForNotification($result.NotificationId)
-
- if ($result -ne $null) {
- Write-Host "Notification was activated: $($result.Activated)"
- Write-Host "Notification was dismissed: $($result.Dismissed)"
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+ if ($result.ClickedButtonId) {
+ Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
+ # Handle button clicks
if ($result.ClickedButtonId -eq "reboot") {
- Write-Host "User chose to reboot now"
- # In a real script, you would initiate a reboot here
+ Write-Host "User chose to reboot now (simulated)"
+ # In a real script, you would reboot the computer here
# Restart-Computer -Force
}
- elseif ($result.Deferred) {
- Write-Host "User deferred the reboot until: $($result.DeferredUntil)"
- Write-Host "Deferral reason: $($result.DeferralReason)"
- }
}
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
+} elseif ($result.Deferred) {
+ Write-Host "Notification was deferred until: $($result.DeferredUntil)"
}
diff --git a/Examples/SimpleNotification.ps1 b/Examples/SimpleNotification.ps1
index 17c0a22..1ca2577 100644
--- a/Examples/SimpleNotification.ps1
+++ b/Examples/SimpleNotification.ps1
@@ -1,49 +1,24 @@
# Example: Simple Notification
# This script demonstrates how to show a simple notification
-# Load the WindowsNotifications assembly
+# Import the module
+$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
+Import-Module $modulePath -Force
+
+# Initialize the notification system
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
-}
-else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
-
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Check if running as SYSTEM
-$isSystem = $notificationManager.IsRunningAsSystem()
-Write-Host "Running as SYSTEM: $isSystem"
-
-# Get interactive user sessions
-$sessions = $notificationManager.GetInteractiveUserSessions()
-Write-Host "Interactive user sessions found: $($sessions.Count)"
-foreach ($session in $sessions) {
- Write-Host " $session"
-}
+Initialize-WindowsNotifications -DllPath $dllPath
# Show a simple notification
-Write-Host "Showing simple notification..."
-$result = $notificationManager.ShowSimpleNotification("Simple Notification", "This is a simple notification from PowerShell")
+$result = Show-Notification -Title "Simple Notification" -Message "This is a simple notification." -TimeoutInSeconds 10
# Display the result
Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
-# If the notification was displayed, wait for interaction
-if ($result.Displayed) {
- Write-Host "Waiting for user interaction..."
- $result = $notificationManager.WaitForNotification($result.NotificationId, 30000)
-
- if ($result -ne $null) {
- Write-Host "Notification was activated: $($result.Activated)"
- Write-Host "Notification was dismissed: $($result.Dismissed)"
- }
- else {
- Write-Host "Timed out waiting for user interaction"
- }
+# If the notification was interacted with, show the details
+if ($result.Activated) {
+ Write-Host "Notification was activated"
+} elseif ($result.Dismissed) {
+ Write-Host "Notification was dismissed"
}
diff --git a/NuGet.config b/NuGet.config
new file mode 100644
index 0000000..6873eb9
--- /dev/null
+++ b/NuGet.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 4d1096b..0db324a 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,26 @@
# Windows Notifications
-A .NET DLL library for PowerShell 5 that displays notifications in user context from SYSTEM, with customization options, deferral support, LiteDB integration, and both synchronous/asynchronous operation modes.
+A .NET DLL library for PowerShell 5 or 7 that displays notifications in user context from SYSTEM, with customization options, deferral support, LiteDB integration, and both synchronous/asynchronous operation modes.
## Features
- **User Context Notifications**: Display notifications in the user context from SYSTEM using user impersonation
- **Interactive Session Detection**: Only show notifications when an interactive user session (console or RDP) is present
- **Customizable Notifications**: Create simple or complex notifications with various customization options
-- **Custom Branding**: Support for custom branding with logos, images, colors, and themes
+- **Custom Branding**: Support for custom branding with logos, images, and attribution
- **Deferral Support**: Allow users to defer notifications (e.g., for system reboots)
- **State Persistence**: Save notification state using embedded LiteDB
-- **PowerShell Integration**: Easily load and use the library in PowerShell 5
+- **PowerShell Integration**: Easily load and use the library in PowerShell 5 or 7
- **Synchronous/Asynchronous Modes**: Run notifications in blocking or non-blocking mode
- **Countdown Display**: Show countdown timers for time-sensitive notifications
- **Deadline Actions**: Configure custom actions to execute when notification deadlines are reached
-- **Logging**: Comprehensive logging of notification events and user interactions
+
+## Project Structure
+
+- `WindowsNotifications/` - Core .NET library
+- `WindowsNotifications.Tests/` - Unit tests
+- `PowerShell/` - PowerShell module
+- `Examples/` - Example scripts
## Requirements
@@ -83,257 +89,14 @@ if ($result.ClickedButtonId) {
}
```
-#### Reboot Notification with Deferrals
+### PowerShell Module
+
+The library includes a PowerShell module that makes it easier to use the Windows Notifications functionality in your PowerShell scripts.
```powershell
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
+# Import the module
+Import-Module WindowsNotifications
-# Show a reboot notification
-$result = $notificationManager.ShowRebootNotification(
- "System Reboot Required",
- "Your system needs to be rebooted to complete updates."
-)
-
-# Check the result
-if ($result.ClickedButtonId -eq "reboot") {
- # Reboot the system
- Restart-Computer -Force
-}
-elseif ($result.Deferred) {
- Write-Host "Reboot deferred until: $($result.DeferredUntil)"
-}
-```
-
-### Advanced Usage
-
-#### Custom Notification Options
-
-```powershell
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Create custom notification options
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "Custom Notification"
-$options.Message = "This is a custom notification"
-$options.TimeoutInSeconds = 30
-$options.Async = $true
-$options.PersistState = $true
-
-# Add buttons
-$button1 = New-Object WindowsNotifications.Models.NotificationButton("OK", "ok")
-$button2 = New-Object WindowsNotifications.Models.NotificationButton("Cancel", "cancel")
-$options.Buttons.Add($button1)
-$options.Buttons.Add($button2)
-
-# Show the notification
-$result = $notificationManager.ShowNotification($options)
-```
-
-#### Asynchronous Notifications
-
-```powershell
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Create notification options with async mode
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "Background Task"
-$options.Message = "A background task is running"
-$options.Async = $true
-
-# Show the notification
-$result = $notificationManager.ShowNotification($options)
-
-# Do some work
-# ...
-
-# Check if the notification has been interacted with
-$currentResult = $notificationManager.GetNotificationResult($result.NotificationId)
-if ($currentResult.Activated) {
- Write-Host "User clicked the notification"
-}
-```
-
-#### Custom Database Location
-
-```powershell
-# Create a notification manager with a custom database path
-$dbPath = "C:\CustomPath\Notifications.db"
-$notificationManager = New-Object WindowsNotifications.NotificationManager($dbPath)
-
-# Show a notification
-$result = $notificationManager.ShowSimpleNotification("Title", "Message")
-```
-
-#### Custom Branded Notifications
-
-```powershell
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Create custom notification options with branding
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "IT Department Notification"
-$options.Message = "Your system has been selected for a security update."
-
-# Set branding properties
-$options.BrandingText = "Contoso IT Department"
-$options.BrandingColor = "#0078D7" # Blue
-$options.AccentColor = "#E81123" # Red
-$options.UseDarkTheme = $true
-
-# Set custom images (file paths or URLs)
-$options.LogoImagePath = "C:\Path\To\Logo.png" # Or "https://example.com/logo.png"
-$options.HeroImagePath = "C:\Path\To\Banner.png"
-$options.AppIconPath = "C:\Path\To\Icon.png"
-
-# Add custom buttons with styling
-$updateButton = New-Object WindowsNotifications.Models.NotificationButton("Install Update", "install")
-$updateButton.BackgroundColor = "#107C10" # Green
-$updateButton.TextColor = "#FFFFFF" # White
-$options.Buttons.Add($updateButton)
-
-# Show the notification
-$result = $notificationManager.ShowNotification($options)
-```
-
-#### Countdown and Deadline Notifications
-
-```powershell
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.NotificationManager
-
-# Create custom notification options
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "System Maintenance Required"
-$options.Message = "Your system needs to restart for maintenance. Please save your work."
-
-# Set deadline (5 minutes from now)
-$options.DeadlineTime = (Get-Date).AddMinutes(5)
-$options.ShowCountdown = $true
-
-# Create deadline action (restart computer)
-$deadlineAction = [WindowsNotifications.Models.DeadlineAction]::ExecuteScript(
- "Write-Host 'System would restart now'; Start-Sleep -Seconds 5"
-)
-$options.DeadlineAction = $deadlineAction
-
-# Enable logging
-$options.EnableLogging = $true
-$options.LogAction = {
- param($logEntry)
- Add-Content -Path "C:\Logs\notifications.log" -Value $logEntry
- Write-Host $logEntry
-}
-
-# Show the notification
-$result = $notificationManager.ShowNotification($options)
-```
-
-## API Reference
-
-### NotificationManager Class
-
-The main entry point for the Windows Notifications library.
-
-#### Constructors
-
-- `NotificationManager()` - Creates a new NotificationManager with the default database path
-- `NotificationManager(string databasePath)` - Creates a new NotificationManager with the specified database path
-
-#### Methods
-
-- `NotificationResult ShowNotification(NotificationOptions options)` - Shows a notification with the specified options
-- `NotificationResult ShowSimpleNotification(string title, string message)` - Shows a simple notification with the specified title and message
-- `NotificationResult ShowNotificationWithButtons(string title, string message, params string[] buttons)` - Shows a notification with buttons
-- `NotificationResult ShowRebootNotification(string title, string message, string rebootButtonText = "Reboot Now", string deferButtonText = "Defer")` - Shows a reboot notification with deferral options
-- `NotificationResult GetNotificationResult(string notificationId)` - Gets the result of a notification
-- `NotificationResult WaitForNotification(string notificationId, int timeout = -1)` - Waits for a notification to complete
-- `List GetAllNotificationResults()` - Gets all notification results from the database
-- `bool DeleteNotificationResult(string notificationId)` - Deletes a notification result from the database
-- `bool DeleteAllNotificationResults()` - Deletes all notification results from the database
-- `string GetDatabaseFilePath()` - Gets the path to the database file
-- `bool IsRunningAsSystem()` - Checks if the current process is running as SYSTEM
-- `List GetInteractiveUserSessions()` - Gets all interactive user sessions
-
-### NotificationOptions Class
-
-Options for configuring a notification.
-
-#### Properties
-
-- `string Title` - The title of the notification
-- `string Message` - The main message body of the notification
-- `string LogoImagePath` - Optional logo image path
-- `string HeroImagePath` - Optional hero image path
-- `string Attribution` - Optional attribution text
-- `int TimeoutInSeconds` - Optional timeout in seconds (0 = no timeout)
-- `List Buttons` - Optional list of buttons to display
-- `bool Async` - Whether to run the notification asynchronously
-- `string Id` - Optional unique identifier for the notification
-- `string Tag` - Optional tag for grouping notifications
-- `string Group` - Optional group name for grouping notifications
-- `DeferralOptions DeferralOptions` - Optional deferral options
-- `bool ShowReminder` - Whether to show a reminder if the notification is not interacted with
-- `int ReminderTimeInMinutes` - Time in minutes after which to show a reminder
-- `bool PersistState` - Whether to persist the notification state in the database
-- `bool EnableLogging` - Whether to enable logging for this notification
-- `Action LogAction` - Optional action to handle logging
-- `Action OnActivated` - Optional action to execute when the notification is activated
-- `Action OnTimeout` - Optional action to execute when the notification times out
-- `Action OnError` - Optional action to execute when an error occurs
-- `DateTime? DeadlineTime` - Optional deadline time for the notification
-- `DeadlineAction DeadlineAction` - The action to take when the deadline is reached
-- `bool ShowCountdown` - Whether to show a countdown timer on the notification
-
-### NotificationResult Class
-
-Represents the result of a notification interaction.
-
-#### Properties
-
-- `string NotificationId` - The unique identifier of the notification
-- `bool Displayed` - Whether the notification was successfully displayed
-- `bool Activated` - Whether the notification was activated (clicked)
-- `bool Dismissed` - Whether the notification was dismissed
-- `string ClickedButtonId` - The ID of the button that was clicked, if any
-- `string ClickedButtonText` - The text of the button that was clicked, if any
-- `string ClickedButtonArgument` - The argument of the button that was clicked, if any
-- `DateTime CreatedTime` - The time when the notification was created
-- `DateTime? InteractionTime` - The time when the notification was interacted with, if any
-- `string ErrorMessage` - Any error message that occurred during the notification process
-- `string ErrorCode` - The error code, if an error occurred
-- `bool Deferred` - Whether the notification was deferred
-- `DateTime? DeferredUntil` - The time when the notification was deferred until, if applicable
-- `string DeferralReason` - The reason for deferral, if applicable
-- `string DismissalReason` - The reason for dismissal, if applicable
-- `string SystemAction` - The system action that was taken (e.g., snooze, dismiss)
-- `bool DeadlineReached` - Whether the deadline was reached
-- `DateTime? DeadlineReachedTime` - The time when the deadline was reached, if applicable
-- `string DeadlineAction` - The action that was taken when the deadline was reached, if applicable
-
-## PowerShell Module
-
-The library includes a PowerShell module that makes it easier to use the Windows Notifications functionality in your PowerShell scripts. The module provides cmdlets for showing notifications, checking results, and managing notification history.
-
-### Installing the PowerShell Module
-
-1. Copy the files from the `PowerShell` directory to one of the following locations:
- - `%UserProfile%\Documents\WindowsPowerShell\Modules\WindowsNotifications` (for the current user)
- - `%ProgramFiles%\WindowsPowerShell\Modules\WindowsNotifications` (for all users)
-
-2. Make sure the `WindowsNotifications.dll` file is in the same directory as the module files.
-
-3. Import the module:
- ```powershell
- Import-Module WindowsNotifications
- ```
-
-### Using the PowerShell Module
-
-```powershell
# Initialize the module
Initialize-WindowsNotifications
@@ -342,9 +105,6 @@ Show-Notification -Title "Hello" -Message "This is a simple notification"
# Show a notification with buttons
$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer"
-
-# Show a reboot notification
-$result = Show-Notification -Title "System Reboot Required" -Message "Your system needs to be rebooted." -RebootButtonText "Reboot Now" -DeferButtonText "Defer"
```
See the [PowerShell/README.md](PowerShell/README.md) file for more information about the PowerShell module.
@@ -365,10 +125,9 @@ See the [Examples](Examples) directory for complete PowerShell script examples:
## Building from Source
1. Clone the repository
-2. Open the solution in Visual Studio
-3. Restore NuGet packages
-4. Build the solution in Release mode
-5. The compiled DLL will be in the `WindowsNotifications\bin\Release` directory
+2. Run the build script: `./build.ps1 -Release`
+3. The compiled DLL will be in the `WindowsNotifications\bin\Release` directory
+4. The PowerShell module will be in the `PowerShell` directory
## License
diff --git a/SimpleWindowsNotifications.sln b/SimpleWindowsNotifications.sln
deleted file mode 100644
index 5c5e451..0000000
--- a/SimpleWindowsNotifications.sln
+++ /dev/null
@@ -1,30 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleWindowsNotifications", "WindowsNotifications\SimpleWindowsNotifications.csproj", "{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsNotifications.Tests", "WindowsNotifications.Tests\WindowsNotifications.Tests.csproj", "{B1A2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.Build.0 = Release|Any CPU
- {B1A2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B1A2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B1A2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B1A2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {F1E2D3C4-B5A6-47C8-B9D0-E1F2A3B4C5D6}
- EndGlobalSection
-EndGlobal
diff --git a/WindowsNotifications.Tests/NotificationManagerTests.cs b/WindowsNotifications.Tests/NotificationManagerTests.cs
index 86c0dfd..bd6e120 100644
--- a/WindowsNotifications.Tests/NotificationManagerTests.cs
+++ b/WindowsNotifications.Tests/NotificationManagerTests.cs
@@ -1,6 +1,7 @@
using NUnit.Framework;
using System;
using System.IO;
+using WindowsNotifications;
using WindowsNotifications.Models;
namespace WindowsNotifications.Tests
@@ -8,203 +9,165 @@ namespace WindowsNotifications.Tests
[TestFixture]
public class NotificationManagerTests
{
- [Test]
- public void Constructor_SetsDefaultDatabasePath()
- {
- // Act
- var manager = new SimpleNotificationManager();
+ private string _testDbPath;
+ private NotificationManager _manager;
- // Assert
- Assert.IsNotNull(manager.DatabasePath);
- Assert.IsTrue(manager.DatabasePath.Contains("WindowsNotifications"));
- Assert.IsTrue(manager.DatabasePath.EndsWith("notifications.db"));
+ [SetUp]
+ public void Setup()
+ {
+ _testDbPath = Path.Combine(Path.GetTempPath(), string.Format("test_notifications_{0}.db", Guid.NewGuid()));
+ _manager = new NotificationManager(_testDbPath);
}
- [Test]
- public void Constructor_SetsCustomDatabasePath()
+ [TearDown]
+ public void TearDown()
{
- // Arrange
- string customPath = Path.Combine(Path.GetTempPath(), "custom.db");
-
- // Act
- var manager = new SimpleNotificationManager(customPath);
-
- // Assert
- Assert.AreEqual(customPath, manager.DatabasePath);
- }
-
- [Test]
- public void ShowNotification_ValidatesOptions()
- {
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act & Assert
- Assert.Throws(() => manager.ShowNotification(null));
-
- var emptyOptions = new NotificationOptions();
- Assert.Throws(() => manager.ShowNotification(emptyOptions));
- }
-
- [Test]
- public void ShowNotification_ReturnsValidResult()
- {
- // Arrange
- var manager = new SimpleNotificationManager();
- var options = new NotificationOptions
+ if (File.Exists(_testDbPath))
{
- Title = "Test Title",
- Message = "Test Message"
- };
-
- // Act
- var result = manager.ShowNotification(options);
-
- // Assert
- Assert.IsNotNull(result);
- Assert.AreEqual(options.Id, result.NotificationId);
- Assert.IsTrue(result.Displayed);
- Assert.IsNotNull(result.CreatedTime);
+ try
+ {
+ File.Delete(_testDbPath);
+ }
+ catch
+ {
+ // Ignore errors
+ }
+ }
}
[Test]
- public void ShowSimpleNotification_ReturnsValidResult()
+ public void NotificationManager_Constructor_DefaultPath_IsCorrect()
{
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- var result = manager.ShowSimpleNotification("Test Title", "Test Message");
+ // Arrange & Act
+ var manager = new NotificationManager();
// Assert
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.NotificationId);
- Assert.IsTrue(result.Displayed);
- Assert.IsNotNull(result.CreatedTime);
+ string dbPath = manager.GetDatabaseFilePath();
+ Assert.IsNotNull(dbPath);
+ Assert.IsTrue(dbPath.Contains("WindowsNotifications"));
+ Assert.IsTrue(dbPath.EndsWith(".db"));
}
[Test]
- public void ShowNotificationWithButtons_ReturnsValidResult()
+ public void NotificationManager_Constructor_CustomPath_IsCorrect()
{
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- var result = manager.ShowNotificationWithButtons("Test Title", "Test Message", "Button 1", "Button 2");
+ // Arrange & Act
+ var customPath = Path.Combine(Path.GetTempPath(), "custom_notifications.db");
+ var manager = new NotificationManager(customPath);
// Assert
- Assert.IsNotNull(result);
- Assert.IsNotNull(result.NotificationId);
- Assert.IsTrue(result.Displayed);
- Assert.IsNotNull(result.CreatedTime);
- Assert.IsTrue(result.Activated);
- Assert.IsNotNull(result.ClickedButtonId);
- Assert.IsNotNull(result.ClickedButtonText);
- Assert.IsNotNull(result.ClickedButtonArgument);
+ Assert.AreEqual(customPath, manager.GetDatabaseFilePath());
}
[Test]
- public void GetDatabaseFilePath_ReturnsDatabasePath()
+ public void NotificationManager_IsRunningAsSystem_ReturnsCorrectValue()
{
- // Arrange
- string customPath = Path.Combine(Path.GetTempPath(), "custom.db");
- var manager = new SimpleNotificationManager(customPath);
-
- // Act
- string path = manager.GetDatabaseFilePath();
-
- // Assert
- Assert.AreEqual(customPath, path);
- }
-
- [Test]
- public void IsRunningAsSystem_ReturnsFalse()
- {
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- bool isSystem = manager.IsRunningAsSystem();
+ // Arrange & Act
+ bool isSystem = _manager.IsRunningAsSystem();
// Assert
+ // This will be false in a test environment, but we're just testing the method returns a value
Assert.IsFalse(isSystem);
}
[Test]
- public void GetInteractiveUserSessions_ReturnsEmptyList()
+ public void NotificationManager_GetInteractiveUserSessions_ReturnsNonNullList()
{
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- var sessions = manager.GetInteractiveUserSessions();
+ // Arrange & Act
+ var sessions = _manager.GetInteractiveUserSessions();
// Assert
Assert.IsNotNull(sessions);
- Assert.AreEqual(0, sessions.Count);
+ // We can't assert the count because it depends on the environment
}
[Test]
- public void GetNotificationResult_ReturnsNull()
+ public void NotificationManager_ShowSimpleNotification_ReturnsResult()
{
// Arrange
- var manager = new SimpleNotificationManager();
+ string title = "Test Title";
+ string message = "Test Message";
// Act
- var result = manager.GetNotificationResult("test-id");
+ var result = _manager.ShowSimpleNotification(title, message);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsNotNull(result.NotificationId);
+ }
+
+ [Test]
+ public void NotificationManager_ShowNotificationWithButtons_ReturnsResult()
+ {
+ // Arrange
+ string title = "Test Title";
+ string message = "Test Message";
+ string[] buttons = new[] { "OK", "Cancel" };
+
+ // Act
+ var result = _manager.ShowNotificationWithButtons(title, message, buttons);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsNotNull(result.NotificationId);
+ }
+
+ [Test]
+ public void NotificationManager_ShowRebootNotification_ReturnsResult()
+ {
+ // Arrange
+ string title = "Test Title";
+ string message = "Test Message";
+
+ // Act
+ var result = _manager.ShowRebootNotification(title, message);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsNotNull(result.NotificationId);
+ }
+
+ [Test]
+ public void NotificationManager_GetNotificationResult_ReturnsNullForNonExistentId()
+ {
+ // Arrange
+ string nonExistentId = Guid.NewGuid().ToString();
+
+ // Act
+ var result = _manager.GetNotificationResult(nonExistentId);
// Assert
Assert.IsNull(result);
}
[Test]
- public void WaitForNotification_ReturnsNull()
+ public void NotificationManager_GetAllNotificationResults_ReturnsNonNullList()
{
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- var result = manager.WaitForNotification("test-id", 100);
-
- // Assert
- Assert.IsNull(result);
- }
-
- [Test]
- public void GetAllNotificationResults_ReturnsEmptyList()
- {
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- var results = manager.GetAllNotificationResults();
+ // Arrange & Act
+ var results = _manager.GetAllNotificationResults();
// Assert
Assert.IsNotNull(results);
- Assert.AreEqual(0, results.Count);
}
[Test]
- public void DeleteNotificationResult_ReturnsTrue()
+ public void NotificationManager_DeleteNotificationResult_ReturnsFalseForNonExistentId()
{
// Arrange
- var manager = new SimpleNotificationManager();
+ string nonExistentId = Guid.NewGuid().ToString();
// Act
- bool result = manager.DeleteNotificationResult("test-id");
+ bool result = _manager.DeleteNotificationResult(nonExistentId);
// Assert
- Assert.IsTrue(result);
+ Assert.IsFalse(result);
}
[Test]
- public void DeleteAllNotificationResults_ReturnsTrue()
+ public void NotificationManager_DeleteAllNotificationResults_ReturnsTrue()
{
- // Arrange
- var manager = new SimpleNotificationManager();
-
- // Act
- bool result = manager.DeleteAllNotificationResults();
+ // Arrange & Act
+ bool result = _manager.DeleteAllNotificationResults();
// Assert
Assert.IsTrue(result);
diff --git a/WindowsNotifications.Tests/NotificationOptionsTests.cs b/WindowsNotifications.Tests/NotificationOptionsTests.cs
index 3de7967..08722a8 100644
--- a/WindowsNotifications.Tests/NotificationOptionsTests.cs
+++ b/WindowsNotifications.Tests/NotificationOptionsTests.cs
@@ -1,6 +1,5 @@
using NUnit.Framework;
using System;
-using System.Linq;
using WindowsNotifications.Models;
namespace WindowsNotifications.Tests
@@ -9,36 +8,41 @@ namespace WindowsNotifications.Tests
public class NotificationOptionsTests
{
[Test]
- public void Constructor_SetsDefaultValues()
+ public void NotificationOptions_DefaultValues_AreCorrect()
{
- // Act
+ // Arrange & Act
var options = new NotificationOptions();
// Assert
- Assert.IsNull(options.Title);
- Assert.IsNull(options.Message);
- Assert.IsNull(options.LogoImagePath);
- Assert.IsNull(options.HeroImagePath);
- Assert.IsNull(options.Attribution);
- Assert.AreEqual(0, options.TimeoutInSeconds);
+ Assert.IsNotNull(options.Buttons);
+ Assert.AreEqual(0, options.Buttons.Count);
Assert.IsFalse(options.Async);
Assert.IsNotNull(options.Id);
Assert.IsTrue(Guid.TryParse(options.Id, out _));
- Assert.IsNull(options.Tag);
- Assert.IsNull(options.Group);
- Assert.IsFalse(options.PersistState);
- Assert.IsTrue(options.EnableLogging);
+ Assert.IsTrue(options.PersistState);
+ Assert.AreEqual(60, options.ReminderTimeInMinutes);
+ Assert.IsFalse(options.ShowReminder);
+ Assert.IsFalse(options.ShowCountdown);
+ Assert.IsFalse(options.EnableLogging);
Assert.IsNull(options.LogAction);
- Assert.IsNotNull(options.Buttons);
- Assert.AreEqual(0, options.Buttons.Count);
+ Assert.IsNull(options.OnActivated);
+ Assert.IsNull(options.OnTimeout);
+ Assert.IsNull(options.OnError);
+ Assert.IsNull(options.DeadlineTime);
+ Assert.IsNull(options.DeadlineAction);
}
[Test]
- public void Properties_CanBeSet()
+ public void NotificationOptions_SetProperties_ValuesAreCorrect()
{
// Arrange
var options = new NotificationOptions();
- Action logAction = (log) => { };
+ var button = new NotificationButton("Test", "test");
+ var deferralOptions = new DeferralOptions();
+ var deadlineAction = DeadlineAction.ExecuteCommand("test");
+ var deadlineTime = DateTime.Now.AddHours(1);
+ Action logAction = (s) => { };
+ Action resultAction = (r) => { };
// Act
options.Title = "Test Title";
@@ -47,13 +51,23 @@ namespace WindowsNotifications.Tests
options.HeroImagePath = "hero.png";
options.Attribution = "Test Attribution";
options.TimeoutInSeconds = 30;
+ options.Buttons.Add(button);
options.Async = true;
- options.Id = "custom-id";
+ options.Id = "test-id";
options.Tag = "test-tag";
options.Group = "test-group";
- options.PersistState = true;
- options.EnableLogging = false;
+ options.DeferralOptions = deferralOptions;
+ options.ShowReminder = true;
+ options.ReminderTimeInMinutes = 15;
+ options.PersistState = false;
+ options.EnableLogging = true;
options.LogAction = logAction;
+ options.OnActivated = resultAction;
+ options.OnTimeout = resultAction;
+ options.OnError = resultAction;
+ options.DeadlineTime = deadlineTime;
+ options.DeadlineAction = deadlineAction;
+ options.ShowCountdown = true;
// Assert
Assert.AreEqual("Test Title", options.Title);
@@ -62,91 +76,24 @@ namespace WindowsNotifications.Tests
Assert.AreEqual("hero.png", options.HeroImagePath);
Assert.AreEqual("Test Attribution", options.Attribution);
Assert.AreEqual(30, options.TimeoutInSeconds);
+ Assert.AreEqual(1, options.Buttons.Count);
+ Assert.AreEqual(button, options.Buttons[0]);
Assert.IsTrue(options.Async);
- Assert.AreEqual("custom-id", options.Id);
+ Assert.AreEqual("test-id", options.Id);
Assert.AreEqual("test-tag", options.Tag);
Assert.AreEqual("test-group", options.Group);
- Assert.IsTrue(options.PersistState);
- Assert.IsFalse(options.EnableLogging);
+ Assert.AreEqual(deferralOptions, options.DeferralOptions);
+ Assert.IsTrue(options.ShowReminder);
+ Assert.AreEqual(15, options.ReminderTimeInMinutes);
+ Assert.IsFalse(options.PersistState);
+ Assert.IsTrue(options.EnableLogging);
Assert.AreEqual(logAction, options.LogAction);
- }
-
- [Test]
- public void Buttons_CanBeAdded()
- {
- // Arrange
- var options = new NotificationOptions();
- var button1 = new NotificationButton("Button 1");
- var button2 = new NotificationButton("Button 2", "custom-id", "custom-arg");
-
- // Act
- options.Buttons.Add(button1);
- options.Buttons.Add(button2);
-
- // Assert
- Assert.AreEqual(2, options.Buttons.Count);
- Assert.AreEqual("Button 1", options.Buttons[0].Text);
- Assert.AreEqual("Button 2", options.Buttons[1].Text);
- Assert.AreEqual("custom-id", options.Buttons[1].Id);
- Assert.AreEqual("custom-arg", options.Buttons[1].Argument);
- }
- }
-
- [TestFixture]
- public class NotificationButtonTests
- {
- [Test]
- public void Constructor_SetsText()
- {
- // Arrange & Act
- var button = new NotificationButton("Button Text");
-
- // Assert
- Assert.AreEqual("Button Text", button.Text);
- Assert.IsNotNull(button.Id);
- Assert.IsTrue(Guid.TryParse(button.Id, out _));
- Assert.AreEqual(button.Id, button.Argument);
- }
-
- [Test]
- public void Constructor_SetsCustomId()
- {
- // Arrange & Act
- var button = new NotificationButton("Button Text", "custom-id");
-
- // Assert
- Assert.AreEqual("Button Text", button.Text);
- Assert.AreEqual("custom-id", button.Id);
- Assert.AreEqual("custom-id", button.Argument);
- }
-
- [Test]
- public void Constructor_SetsCustomArgument()
- {
- // Arrange & Act
- var button = new NotificationButton("Button Text", "custom-id", "custom-arg");
-
- // Assert
- Assert.AreEqual("Button Text", button.Text);
- Assert.AreEqual("custom-id", button.Id);
- Assert.AreEqual("custom-arg", button.Argument);
- }
-
- [Test]
- public void Properties_CanBeSet()
- {
- // Arrange
- var button = new NotificationButton("Initial Text");
-
- // Act
- button.Text = "Updated Text";
- button.Id = "updated-id";
- button.Argument = "updated-arg";
-
- // Assert
- Assert.AreEqual("Updated Text", button.Text);
- Assert.AreEqual("updated-id", button.Id);
- Assert.AreEqual("updated-arg", button.Argument);
+ Assert.AreEqual(resultAction, options.OnActivated);
+ Assert.AreEqual(resultAction, options.OnTimeout);
+ Assert.AreEqual(resultAction, options.OnError);
+ Assert.AreEqual(deadlineTime, options.DeadlineTime);
+ Assert.AreEqual(deadlineAction, options.DeadlineAction);
+ Assert.IsTrue(options.ShowCountdown);
}
}
}
diff --git a/WindowsNotifications.Tests/NotificationResultTests.cs b/WindowsNotifications.Tests/NotificationResultTests.cs
index 5c1c7e5..f0d673f 100644
--- a/WindowsNotifications.Tests/NotificationResultTests.cs
+++ b/WindowsNotifications.Tests/NotificationResultTests.cs
@@ -8,82 +8,84 @@ namespace WindowsNotifications.Tests
public class NotificationResultTests
{
[Test]
- public void Constructor_SetsNotificationId()
+ public void NotificationResult_DefaultValues_AreCorrect()
{
- // Arrange
- string notificationId = "test-id";
-
- // Act
- var result = new NotificationResult(notificationId);
-
- // Assert
- Assert.AreEqual(notificationId, result.NotificationId);
- }
-
- [Test]
- public void Constructor_SetsDefaultValues()
- {
- // Arrange
- string notificationId = "test-id";
-
- // Act
- var result = new NotificationResult(notificationId);
+ // Arrange & Act
+ var result = new NotificationResult();
// Assert
+ Assert.IsNull(result.NotificationId);
Assert.IsFalse(result.Displayed);
Assert.IsFalse(result.Activated);
Assert.IsFalse(result.Dismissed);
Assert.IsNull(result.ClickedButtonId);
Assert.IsNull(result.ClickedButtonText);
Assert.IsNull(result.ClickedButtonArgument);
- Assert.IsNull(result.ErrorMessage);
+ Assert.IsTrue((DateTime.Now - result.CreatedTime).TotalSeconds < 1);
Assert.IsNull(result.InteractionTime);
- // CreatedTime should be close to now
- Assert.Less((DateTime.Now - result.CreatedTime).TotalSeconds, 5);
+ Assert.IsNull(result.ErrorMessage);
+ Assert.IsNull(result.ErrorCode);
+ Assert.IsFalse(result.Deferred);
+ Assert.IsNull(result.DeferredUntil);
+ Assert.IsNull(result.DeferralReason);
+ Assert.IsNull(result.DismissalReason);
+ Assert.IsNull(result.SystemAction);
+ Assert.IsFalse(result.DeadlineReached);
+ Assert.IsNull(result.DeadlineReachedTime);
+ Assert.IsNull(result.DeadlineAction);
}
[Test]
- public void Error_CreatesErrorResult()
+ public void NotificationResult_SetProperties_ValuesAreCorrect()
{
// Arrange
- string notificationId = "test-id";
- string errorMessage = "Test error message";
-
- // Act
- var result = NotificationResult.Error(notificationId, errorMessage);
-
- // Assert
- Assert.AreEqual(notificationId, result.NotificationId);
- Assert.AreEqual(errorMessage, result.ErrorMessage);
- Assert.IsFalse(result.Displayed);
- }
-
- [Test]
- public void Properties_CanBeSet()
- {
- // Arrange
- var result = new NotificationResult("test-id");
- DateTime interactionTime = DateTime.Now;
+ var result = new NotificationResult();
+ var createdTime = DateTime.Now.AddMinutes(-5);
+ var interactionTime = DateTime.Now.AddMinutes(-2);
+ var deferredUntil = DateTime.Now.AddHours(1);
+ var deadlineReachedTime = DateTime.Now.AddMinutes(-1);
// Act
+ result.NotificationId = "test-id";
result.Displayed = true;
result.Activated = true;
result.Dismissed = true;
- result.ClickedButtonId = "button-id";
- result.ClickedButtonText = "Button Text";
- result.ClickedButtonArgument = "button-arg";
- result.ErrorMessage = "Error message";
+ result.ClickedButtonId = "button1";
+ result.ClickedButtonText = "OK";
+ result.ClickedButtonArgument = "ok";
+ result.CreatedTime = createdTime;
result.InteractionTime = interactionTime;
+ result.ErrorMessage = "Test error";
+ result.ErrorCode = "E001";
+ result.Deferred = true;
+ result.DeferredUntil = deferredUntil;
+ result.DeferralReason = "User requested";
+ result.DismissalReason = "User dismissed";
+ result.SystemAction = "snooze";
+ result.DeadlineReached = true;
+ result.DeadlineReachedTime = deadlineReachedTime;
+ result.DeadlineAction = "restart";
// Assert
+ Assert.AreEqual("test-id", result.NotificationId);
Assert.IsTrue(result.Displayed);
Assert.IsTrue(result.Activated);
Assert.IsTrue(result.Dismissed);
- Assert.AreEqual("button-id", result.ClickedButtonId);
- Assert.AreEqual("Button Text", result.ClickedButtonText);
- Assert.AreEqual("button-arg", result.ClickedButtonArgument);
- Assert.AreEqual("Error message", result.ErrorMessage);
+ Assert.AreEqual("button1", result.ClickedButtonId);
+ Assert.AreEqual("OK", result.ClickedButtonText);
+ Assert.AreEqual("ok", result.ClickedButtonArgument);
+ Assert.AreEqual(createdTime, result.CreatedTime);
Assert.AreEqual(interactionTime, result.InteractionTime);
+ Assert.AreEqual("Test error", result.ErrorMessage);
+ Assert.AreEqual("E001", result.ErrorCode);
+ Assert.IsTrue(result.Deferred);
+ Assert.AreEqual(deferredUntil, result.DeferredUntil);
+ Assert.AreEqual("User requested", result.DeferralReason);
+ Assert.AreEqual("User dismissed", result.DismissalReason);
+ Assert.AreEqual("snooze", result.SystemAction);
+ Assert.IsTrue(result.DeadlineReached);
+ Assert.AreEqual(deadlineReachedTime, result.DeadlineReachedTime);
+ Assert.AreEqual("restart", result.DeadlineAction);
}
}
}
diff --git a/WindowsNotifications.Tests/Properties/AssemblyInfo.cs b/WindowsNotifications.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..872b186
--- /dev/null
+++ b/WindowsNotifications.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("WindowsNotifications.Tests")]
+[assembly: AssemblyDescription("Tests for the Windows Notifications library")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("WindowsNotifications.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("b1c2d3e4-f5g6-47a8-b9c0-d1e2f3a4b5c6")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/WindowsNotifications.Tests/UnitTest1.cs b/WindowsNotifications.Tests/UnitTest1.cs
new file mode 100644
index 0000000..f0333c3
--- /dev/null
+++ b/WindowsNotifications.Tests/UnitTest1.cs
@@ -0,0 +1,18 @@
+using NUnit.Framework;
+
+namespace WindowsNotifications.Tests
+{
+ public class Tests
+ {
+ [SetUp]
+ public void Setup()
+ {
+ }
+
+ [Test]
+ public void Test1()
+ {
+ Assert.Pass();
+ }
+ }
+}
\ No newline at end of file
diff --git a/WindowsNotifications.Tests/WindowsNotifications.Tests.csproj b/WindowsNotifications.Tests/WindowsNotifications.Tests.csproj
index 58e37cd..d696e86 100644
--- a/WindowsNotifications.Tests/WindowsNotifications.Tests.csproj
+++ b/WindowsNotifications.Tests/WindowsNotifications.Tests.csproj
@@ -1,18 +1,73 @@
-
-
+
+
+
+
+
- net472
- false
+ Debug
+ AnyCPU
+ {B1C2D3E4-F5G6-47A8-B9C0-D1E2F3A4B5C6}
+ Library
+ Properties
+ WindowsNotifications.Tests
+ WindowsNotifications.Tests
+ v4.7.2
+ 512
+ true
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
-
-
-
-
+
+ ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
+
+
+ {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}
+ WindowsNotifications
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
diff --git a/WindowsNotifications.Tests/packages.config b/WindowsNotifications.Tests/packages.config
new file mode 100644
index 0000000..217d892
--- /dev/null
+++ b/WindowsNotifications.Tests/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/WindowsNotifications.sln b/WindowsNotifications.sln
index d54fb3d..fb5ff82 100644
--- a/WindowsNotifications.sln
+++ b/WindowsNotifications.sln
@@ -1,24 +1,28 @@
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsNotifications", "WindowsNotifications\WindowsNotifications.csproj", "{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsNotifications.Tests", "WindowsNotifications.Tests\WindowsNotifications.Tests.csproj", "{FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {F1E2D3C4-B5A6-47C8-B9D0-E1F2A3B4C5D6}
+ {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/WindowsNotifications/Cmdlets/ClearAllNotificationResultsCmdlet.cs b/WindowsNotifications/Cmdlets/ClearAllNotificationResultsCmdlet.cs
new file mode 100644
index 0000000..4721c5c
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/ClearAllNotificationResultsCmdlet.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Clears all notification results.
+ /// Clears all notification results from the database.
+ ///
+ [Cmdlet(VerbsCommon.Clear, "AllNotificationResults")]
+ [OutputType(typeof(bool))]
+ public class ClearAllNotificationResultsCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var result = _notificationManager.DeleteAllNotificationResults();
+ WriteObject(result);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "ClearAllNotificationResultsError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/GetAllNotificationResultsCmdlet.cs b/WindowsNotifications/Cmdlets/GetAllNotificationResultsCmdlet.cs
new file mode 100644
index 0000000..7694893
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/GetAllNotificationResultsCmdlet.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Gets all notification results.
+ /// Gets all notification results from the database.
+ ///
+ [Cmdlet(VerbsCommon.Get, "AllNotificationResults")]
+ [OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
+ public class GetAllNotificationResultsCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var results = _notificationManager.GetAllNotificationResults();
+ WriteObject(results, true);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "GetAllNotificationResultsError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/GetInteractiveUserSessionsCmdlet.cs b/WindowsNotifications/Cmdlets/GetInteractiveUserSessionsCmdlet.cs
new file mode 100644
index 0000000..17333af
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/GetInteractiveUserSessionsCmdlet.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Gets interactive user sessions.
+ /// Gets all interactive user sessions.
+ ///
+ [Cmdlet(VerbsCommon.Get, "InteractiveUserSessions")]
+ [OutputType(typeof(string))]
+ public class GetInteractiveUserSessionsCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var sessions = _notificationManager.GetInteractiveUserSessions();
+ WriteObject(sessions, true);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "GetInteractiveUserSessionsError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/GetNotificationResultCmdlet.cs b/WindowsNotifications/Cmdlets/GetNotificationResultCmdlet.cs
new file mode 100644
index 0000000..87affef
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/GetNotificationResultCmdlet.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Gets a notification result.
+ /// Gets the result of a notification by its ID.
+ ///
+ [Cmdlet(VerbsCommon.Get, "NotificationResult")]
+ [OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
+ public class GetNotificationResultCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The ID of the notification.
+ ///
+ [Parameter(Mandatory = true, Position = 0)]
+ public string NotificationId { get; set; }
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var result = _notificationManager.GetNotificationResult(NotificationId);
+ WriteObject(result);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "GetNotificationResultError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/InitializeWindowsNotificationsCmdlet.cs b/WindowsNotifications/Cmdlets/InitializeWindowsNotificationsCmdlet.cs
new file mode 100644
index 0000000..90bcf0f
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/InitializeWindowsNotificationsCmdlet.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Initializes the Windows Notifications system.
+ /// Initializes the Windows Notifications system with the specified database path.
+ ///
+ [Cmdlet(VerbsData.Initialize, "WindowsNotifications")]
+ [OutputType(typeof(bool))]
+ public class InitializeWindowsNotificationsCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+
+ WriteVerbose("Windows Notifications initialized successfully.");
+ WriteVerbose(string.Format("Database path: {0}", _notificationManager.GetDatabaseFilePath()));
+
+ // Check if running as SYSTEM
+ bool isSystem = _notificationManager.IsRunningAsSystem();
+ WriteVerbose(string.Format("Running as SYSTEM: {0}", isSystem));
+
+ // Check for interactive user sessions
+ var sessions = _notificationManager.GetInteractiveUserSessions();
+ WriteVerbose(string.Format("Interactive user sessions: {0}", sessions.Count));
+ foreach (var session in sessions)
+ {
+ WriteVerbose(string.Format(" - {0}", session));
+ }
+
+ WriteObject(true);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "InitializeWindowsNotificationsError", ErrorCategory.NotSpecified, null));
+ WriteObject(false);
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/RemoveNotificationResultCmdlet.cs b/WindowsNotifications/Cmdlets/RemoveNotificationResultCmdlet.cs
new file mode 100644
index 0000000..458ff8f
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/RemoveNotificationResultCmdlet.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Removes a notification result.
+ /// Removes a notification result from the database.
+ ///
+ [Cmdlet(VerbsCommon.Remove, "NotificationResult")]
+ [OutputType(typeof(bool))]
+ public class RemoveNotificationResultCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The ID of the notification.
+ ///
+ [Parameter(Mandatory = true, Position = 0)]
+ public string NotificationId { get; set; }
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var result = _notificationManager.DeleteNotificationResult(NotificationId);
+ WriteObject(result);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "RemoveNotificationResultError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/ShowNotificationCmdlet.cs b/WindowsNotifications/Cmdlets/ShowNotificationCmdlet.cs
new file mode 100644
index 0000000..75873c2
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/ShowNotificationCmdlet.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Shows a notification.
+ /// Shows a notification with the specified options.
+ ///
+ [Cmdlet(VerbsCommon.Show, "Notification")]
+ [OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
+ public class ShowNotificationCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The title of the notification.
+ ///
+ [Parameter(Mandatory = true, Position = 0)]
+ public string Title { get; set; }
+
+ ///
+ /// The message body of the notification.
+ ///
+ [Parameter(Mandatory = true, Position = 1)]
+ public string Message { get; set; }
+
+ ///
+ /// The buttons to display on the notification.
+ ///
+ [Parameter(Mandatory = false)]
+ public string[] Buttons { get; set; }
+
+ ///
+ /// Whether to run the notification asynchronously.
+ ///
+ [Parameter(Mandatory = false)]
+ public SwitchParameter Async { get; set; }
+
+ ///
+ /// The timeout in seconds (0 = no timeout).
+ ///
+ [Parameter(Mandatory = false)]
+ public int TimeoutInSeconds { get; set; }
+
+ ///
+ /// The path to the logo image.
+ ///
+ [Parameter(Mandatory = false)]
+ public string LogoImagePath { get; set; }
+
+ ///
+ /// The path to the hero image.
+ ///
+ [Parameter(Mandatory = false)]
+ public string HeroImagePath { get; set; }
+
+ ///
+ /// The attribution text.
+ ///
+ [Parameter(Mandatory = false)]
+ public string Attribution { get; set; }
+
+ ///
+ /// The deadline time for the notification.
+ ///
+ [Parameter(Mandatory = false)]
+ public DateTime? DeadlineTime { get; set; }
+
+ ///
+ /// Whether to show a countdown timer on the notification.
+ ///
+ [Parameter(Mandatory = false)]
+ public SwitchParameter ShowCountdown { get; set; }
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var options = new WindowsNotifications.Models.NotificationOptions
+ {
+ Title = Title,
+ Message = Message,
+ Async = Async.IsPresent,
+ TimeoutInSeconds = TimeoutInSeconds,
+ LogoImagePath = LogoImagePath,
+ HeroImagePath = HeroImagePath,
+ Attribution = Attribution,
+ DeadlineTime = DeadlineTime,
+ ShowCountdown = ShowCountdown.IsPresent
+ };
+
+ if (Buttons != null && Buttons.Length > 0)
+ {
+ for (int i = 0; i < Buttons.Length; i++)
+ {
+ options.Buttons.Add(new WindowsNotifications.Models.NotificationButton(Buttons[i], string.Format("button{0}", i), Buttons[i]));
+ }
+ }
+
+ var result = _notificationManager.ShowNotification(options);
+ WriteObject(result);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "ShowNotificationError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/TestSystemContextCmdlet.cs b/WindowsNotifications/Cmdlets/TestSystemContextCmdlet.cs
new file mode 100644
index 0000000..7479289
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/TestSystemContextCmdlet.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Tests if running as SYSTEM.
+ /// Tests if the current process is running as SYSTEM.
+ ///
+ [Cmdlet(VerbsDiagnostic.Test, "SystemContext")]
+ [OutputType(typeof(bool))]
+ public class TestSystemContextCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var result = _notificationManager.IsRunningAsSystem();
+ WriteObject(result);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "TestSystemContextError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Cmdlets/WaitNotificationCmdlet.cs b/WindowsNotifications/Cmdlets/WaitNotificationCmdlet.cs
new file mode 100644
index 0000000..b09c140
--- /dev/null
+++ b/WindowsNotifications/Cmdlets/WaitNotificationCmdlet.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Management.Automation;
+
+namespace WindowsNotifications.Cmdlets
+{
+ ///
+ /// Waits for a notification to complete.
+ /// Waits for a notification to complete and returns the result.
+ ///
+ [Cmdlet(VerbsLifecycle.Wait, "Notification")]
+ [OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
+ public class WaitNotificationCmdlet : PSCmdlet
+ {
+ private WindowsNotifications.NotificationManager _notificationManager;
+
+ ///
+ /// The ID of the notification.
+ ///
+ [Parameter(Mandatory = true, Position = 0)]
+ public string NotificationId { get; set; }
+
+ ///
+ /// The timeout in seconds (-1 = wait indefinitely).
+ ///
+ [Parameter(Mandatory = false)]
+ public int TimeoutInSeconds { get; set; }
+
+ ///
+ /// The path to the database file.
+ ///
+ [Parameter(Mandatory = false)]
+ public string DatabasePath { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ _notificationManager = string.IsNullOrEmpty(DatabasePath)
+ ? new NotificationManager()
+ : new NotificationManager(DatabasePath);
+
+ // Set default timeout to -1 (wait indefinitely)
+ if (TimeoutInSeconds == 0)
+ {
+ TimeoutInSeconds = -1;
+ }
+ }
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ var result = _notificationManager.WaitForNotification(NotificationId, TimeoutInSeconds * 1000);
+ WriteObject(result);
+ }
+ catch (Exception ex)
+ {
+ WriteError(new ErrorRecord(ex, "WaitNotificationError", ErrorCategory.NotSpecified, null));
+ }
+ }
+ }
+}
diff --git a/WindowsNotifications/Models/DeadlineAction.cs b/WindowsNotifications/Models/DeadlineAction.cs
index a63c224..549d820 100644
--- a/WindowsNotifications/Models/DeadlineAction.cs
+++ b/WindowsNotifications/Models/DeadlineAction.cs
@@ -1,79 +1,51 @@
using System;
-using System.Diagnostics;
namespace WindowsNotifications.Models
{
///
- /// Represents an action to take when a notification deadline is reached
+ /// Represents an action to take when a notification deadline is reached.
///
public class DeadlineAction
{
///
- /// The type of action to take
+ /// Gets or sets the type of deadline action.
///
- public DeadlineActionType ActionType { get; set; } = DeadlineActionType.None;
+ public DeadlineActionType ActionType { get; set; }
///
- /// The command to execute (for Process action type)
+ /// Gets or sets the command to execute when the deadline is reached.
///
public string Command { get; set; }
///
- /// The arguments for the command (for Process action type)
- ///
- public string Arguments { get; set; }
-
- ///
- /// The URL to open (for Url action type)
- ///
- public string Url { get; set; }
-
- ///
- /// The script to execute (for Script action type)
+ /// Gets or sets the script to execute when the deadline is reached.
///
public string Script { get; set; }
///
- /// The custom action to execute (for Custom action type)
+ /// Gets or sets the action to execute when the deadline is reached.
///
- public Action CustomAction { get; set; }
+ public Action Action { get; set; }
///
- /// Creates a new DeadlineAction with no action
+ /// Creates a new deadline action that executes a command.
///
- public DeadlineAction()
- {
- }
-
- ///
- /// Creates a new DeadlineAction that runs a process
- ///
- /// The command to execute
- /// The arguments for the command
- public DeadlineAction(string command, string arguments = null)
- {
- ActionType = DeadlineActionType.Process;
- Command = command;
- Arguments = arguments;
- }
-
- ///
- /// Creates a new DeadlineAction that opens a URL
- ///
- /// The URL to open
- public static DeadlineAction OpenUrl(string url)
+ /// The command to execute.
+ /// A new deadline action.
+ public static DeadlineAction ExecuteCommand(string command)
{
return new DeadlineAction
{
- ActionType = DeadlineActionType.Url,
- Url = url
+ ActionType = DeadlineActionType.Command,
+ Command = command
};
}
///
- /// Creates a new DeadlineAction that executes a PowerShell script
+ /// Creates a new deadline action that executes a PowerShell script.
///
- /// The PowerShell script to execute
+ /// The PowerShell script to execute.
+ /// A new deadline action.
public static DeadlineAction ExecuteScript(string script)
{
return new DeadlineAction
@@ -84,89 +56,43 @@ namespace WindowsNotifications.Models
}
///
- /// Creates a new DeadlineAction that executes a custom action
+ /// Creates a new deadline action that executes a .NET action.
///
- /// The custom action to execute
- public static DeadlineAction ExecuteCustomAction(Action action)
+ /// The action to execute.
+ /// A new deadline action.
+ public static DeadlineAction ExecuteAction(Action action)
{
return new DeadlineAction
{
- ActionType = DeadlineActionType.Custom,
- CustomAction = action
+ ActionType = DeadlineActionType.Action,
+ Action = action
};
}
-
- ///
- /// Executes the deadline action
- ///
- /// The notification result
- public void Execute(NotificationResult result)
- {
- try
- {
- switch (ActionType)
- {
- case DeadlineActionType.Process:
- if (!string.IsNullOrEmpty(Command))
- {
- Process.Start(Command, Arguments ?? string.Empty);
- }
- break;
-
- case DeadlineActionType.Url:
- if (!string.IsNullOrEmpty(Url))
- {
- Process.Start(Url);
- }
- break;
-
- case DeadlineActionType.Script:
- if (!string.IsNullOrEmpty(Script))
- {
- Process.Start("powershell.exe", $"-ExecutionPolicy Bypass -Command \"{Script}\"");
- }
- break;
-
- case DeadlineActionType.Custom:
- CustomAction?.Invoke(result);
- break;
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error executing deadline action: {ex.Message}");
- }
- }
}
///
- /// The type of action to take when a deadline is reached
+ /// Defines the types of deadline actions.
///
public enum DeadlineActionType
{
///
- /// No action
+ /// No action.
///
None,
///
- /// Run a process
+ /// Execute a command.
///
- Process,
+ Command,
///
- /// Open a URL
- ///
- Url,
-
- ///
- /// Execute a PowerShell script
+ /// Execute a PowerShell script.
///
Script,
///
- /// Execute a custom action
+ /// Execute a .NET action.
///
- Custom
+ Action
}
}
diff --git a/WindowsNotifications/Models/DeferralOptions.cs b/WindowsNotifications/Models/DeferralOptions.cs
index d169bac..c561aeb 100644
--- a/WindowsNotifications/Models/DeferralOptions.cs
+++ b/WindowsNotifications/Models/DeferralOptions.cs
@@ -4,112 +4,63 @@ using System.Collections.Generic;
namespace WindowsNotifications.Models
{
///
- /// Options for configuring notification deferrals
+ /// Options for configuring notification deferrals.
///
public class DeferralOptions
{
///
- /// Whether deferrals are enabled for this notification
+ /// Gets or sets whether deferrals are enabled.
///
public bool Enabled { get; set; } = true;
///
- /// The maximum number of times the notification can be deferred
+ /// Gets or sets the maximum number of deferrals allowed.
///
public int MaxDeferrals { get; set; } = 3;
///
- /// The available deferral options to show to the user
+ /// Gets or sets the default deferral time in minutes.
///
- public List DeferralChoices { get; set; } = new List();
+ public int DefaultDeferralTimeInMinutes { get; set; } = 60;
///
- /// The text to display for the deferral button
+ /// Gets or sets the list of predefined deferral options in minutes.
+ ///
+ public List DeferralOptions { get; set; } = new List { 15, 30, 60, 240, 480 };
+
+ ///
+ /// Gets or sets the text for the deferral button.
///
public string DeferButtonText { get; set; } = "Defer";
///
- /// The text to display for the deferral selection prompt
+ /// Gets or sets the text for the deferral dropdown.
///
- public string DeferralPrompt { get; set; } = "Defer until:";
+ public string DeferDropdownText { get; set; } = "Defer for:";
///
- /// Whether to schedule a reminder when the notification is deferred
+ /// Gets or sets the format for the deferral option text.
///
- public bool ScheduleReminder { get; set; } = true;
+ public string DeferralOptionFormat { get; set; } = "{0} minutes";
///
- /// Whether to enforce the maximum number of deferrals
+ /// Gets or sets the format for the deferral option text when the time is in hours.
///
- public bool EnforceMaxDeferrals { get; set; } = true;
+ public string DeferralOptionHourFormat { get; set; } = "{0} hours";
///
- /// The action to take when the maximum number of deferrals is reached
+ /// Gets or sets the absolute deadline after which no more deferrals are allowed.
///
- public DeadlineAction MaxDeferralsAction { get; set; }
+ public DateTime? AbsoluteDeadline { get; set; }
///
- /// The current number of times this notification has been deferred
+ /// Gets or sets the text to display when the absolute deadline is approaching.
///
- public int CurrentDeferralCount { get; set; } = 0;
+ public string DeadlineApproachingText { get; set; } = "Final deadline approaching. No further deferrals will be allowed.";
///
- /// Creates a new DeferralOptions instance with default values
+ /// Gets or sets the time in minutes before the absolute deadline when the deadline approaching message should be shown.
///
- public DeferralOptions()
- {
- // Add default deferral choices
- DeferralChoices.Add(new DeferralOption("30 minutes", TimeSpan.FromMinutes(30)));
- DeferralChoices.Add(new DeferralOption("1 hour", TimeSpan.FromHours(1)));
- DeferralChoices.Add(new DeferralOption("4 hours", TimeSpan.FromHours(4)));
- DeferralChoices.Add(new DeferralOption("Tomorrow", TimeSpan.FromDays(1)));
-
- // Set default max deferrals action
- MaxDeferralsAction = new DeadlineAction();
- }
-
- ///
- /// Creates a new DeferralOptions instance with custom deferral choices
- ///
- /// The deferral choices to offer
- public DeferralOptions(List deferralChoices)
- {
- DeferralChoices = deferralChoices;
- }
- }
-
- ///
- /// Represents a single deferral option
- ///
- public class DeferralOption
- {
- ///
- /// The text to display for this deferral option
- ///
- public string Text { get; set; }
-
- ///
- /// The time span to defer for
- ///
- public TimeSpan DeferralTime { get; set; }
-
- ///
- /// Optional identifier for this deferral option
- ///
- public string Id { get; set; } = Guid.NewGuid().ToString();
-
- ///
- /// Creates a new deferral option
- ///
- /// The text to display
- /// The time span to defer for
- /// Optional identifier
- public DeferralOption(string text, TimeSpan deferralTime, string id = null)
- {
- Text = text;
- DeferralTime = deferralTime;
- if (!string.IsNullOrEmpty(id))
- Id = id;
- }
+ public int DeadlineApproachingWarningMinutes { get; set; } = 120;
}
}
diff --git a/WindowsNotifications/Models/NotificationButton.cs b/WindowsNotifications/Models/NotificationButton.cs
new file mode 100644
index 0000000..410b311
--- /dev/null
+++ b/WindowsNotifications/Models/NotificationButton.cs
@@ -0,0 +1,64 @@
+namespace WindowsNotifications.Models
+{
+ ///
+ /// Represents a button in a notification.
+ ///
+ public class NotificationButton
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotificationButton()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified text and ID.
+ ///
+ /// The text to display on the button.
+ /// The unique identifier for the button.
+ public NotificationButton(string text, string id)
+ {
+ Text = text;
+ Id = id;
+ }
+
+ ///
+ /// Initializes a new instance of the class with the specified text, ID, and argument.
+ ///
+ /// The text to display on the button.
+ /// The unique identifier for the button.
+ /// The argument to return when the button is clicked.
+ public NotificationButton(string text, string id, string argument)
+ {
+ Text = text;
+ Id = id;
+ Argument = argument;
+ }
+
+ ///
+ /// Gets or sets the text to display on the button.
+ ///
+ public string Text { get; set; }
+
+ ///
+ /// Gets or sets the unique identifier for the button.
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// Gets or sets the argument to return when the button is clicked.
+ ///
+ public string Argument { get; set; }
+
+ ///
+ /// Gets or sets the background color of the button (hex format: #RRGGBB).
+ ///
+ public string BackgroundColor { get; set; }
+
+ ///
+ /// Gets or sets the foreground (text) color of the button (hex format: #RRGGBB).
+ ///
+ public string ForegroundColor { get; set; }
+ }
+}
diff --git a/WindowsNotifications/Models/NotificationOptions.cs b/WindowsNotifications/Models/NotificationOptions.cs
index 70470d0..4c17fca 100644
--- a/WindowsNotifications/Models/NotificationOptions.cs
+++ b/WindowsNotifications/Models/NotificationOptions.cs
@@ -4,286 +4,123 @@ using System.Collections.Generic;
namespace WindowsNotifications.Models
{
///
- /// Options for configuring a notification
+ /// Options for configuring a notification.
///
public class NotificationOptions
{
///
- /// The title of the notification
+ /// Gets or sets the title of the notification.
///
public string Title { get; set; }
///
- /// The main message body of the notification
+ /// Gets or sets the main message body of the notification.
///
public string Message { get; set; }
///
- /// Optional logo image path or URL
+ /// Gets or sets the optional logo image path.
///
public string LogoImagePath { get; set; }
///
- /// Optional hero image path or URL
+ /// Gets or sets the optional hero image path.
///
public string HeroImagePath { get; set; }
///
- /// Optional inline image path or URL
- ///
- public string InlineImagePath { get; set; }
-
- ///
- /// Optional app icon path or URL
- ///
- public string AppIconPath { get; set; }
-
- ///
- /// Optional badge logo path or URL
- ///
- public string BadgeLogoPath { get; set; }
-
- ///
- /// Optional background image path or URL for the toast
- ///
- public string BackgroundImagePath { get; set; }
-
- ///
- /// Optional attribution text
+ /// Gets or sets the optional attribution text.
///
public string Attribution { get; set; }
///
- /// Optional timeout in seconds. If set to 0, the notification will not timeout.
+ /// Gets or sets the optional timeout in seconds (0 = no timeout).
///
- public int TimeoutInSeconds { get; set; } = 0;
+ public int TimeoutInSeconds { get; set; }
///
- /// Optional list of buttons to display on the notification
+ /// Gets or sets the optional list of buttons to display.
///
public List Buttons { get; set; } = new List();
///
- /// Whether to run the notification asynchronously
+ /// Gets or sets whether to run the notification asynchronously.
///
- public bool Async { get; set; } = false;
+ public bool Async { get; set; }
///
- /// Optional scenario for the notification (alarm, reminder, incomingCall, urgent)
- ///
- public string Scenario { get; set; }
-
- ///
- /// Optional launch argument for the notification
- ///
- public string LaunchArgument { get; set; }
-
- ///
- /// Optional audio source for the notification
- ///
- public string AudioSource { get; set; }
-
- ///
- /// Whether to loop the audio
- ///
- public bool LoopAudio { get; set; } = false;
-
- ///
- /// Optional custom branding text (e.g., company or department name)
- ///
- public string BrandingText { get; set; }
-
- ///
- /// Optional custom branding color (hex format: #RRGGBB)
- ///
- public string BrandingColor { get; set; }
-
- ///
- /// Optional custom accent color for the notification (hex format: #RRGGBB)
- ///
- public string AccentColor { get; set; }
-
- ///
- /// Optional custom font family for the notification text
- ///
- public string FontFamily { get; set; }
-
- ///
- /// Whether to use dark theme for the notification
- ///
- public bool UseDarkTheme { get; set; } = false;
-
- ///
- /// Whether to show the notification in silent mode (no sound)
- ///
- public bool SilentMode { get; set; } = false;
-
- ///
- /// Whether to show a progress bar on the notification
- ///
- public bool ShowProgressBar { get; set; } = false;
-
- ///
- /// The progress value (0-100) for the progress bar
- ///
- public int ProgressValue { get; set; } = 0;
-
- ///
- /// The status text for the progress bar
- ///
- public string ProgressStatus { get; set; }
-
- ///
- /// Whether to show a countdown timer on the notification
- ///
- public bool ShowCountdown { get; set; } = false;
-
- ///
- /// The deadline time for the notification
- ///
- public DateTime? DeadlineTime { get; set; }
-
- ///
- /// The action to take when the deadline is reached
- ///
- public DeadlineAction DeadlineAction { get; set; }
-
- ///
- /// Optional unique identifier for the notification
+ /// Gets or sets the optional unique identifier for the notification.
///
public string Id { get; set; } = Guid.NewGuid().ToString();
///
- /// Optional tag for grouping notifications
+ /// Gets or sets the optional tag for grouping notifications.
///
public string Tag { get; set; }
///
- /// Optional group name for grouping notifications
+ /// Gets or sets the optional group name for grouping notifications.
///
public string Group { get; set; }
///
- /// Optional deferral options for notifications that can be deferred
+ /// Gets or sets the optional deferral options.
///
public DeferralOptions DeferralOptions { get; set; }
///
- /// Whether to show a reminder if the notification is not interacted with
+ /// Gets or sets whether to show a reminder if the notification is not interacted with.
///
- public bool ShowReminder { get; set; } = false;
+ public bool ShowReminder { get; set; }
///
- /// Time in minutes after which to show a reminder if ShowReminder is true
+ /// Gets or sets the time in minutes after which to show a reminder.
///
public int ReminderTimeInMinutes { get; set; } = 60;
///
- /// Whether to persist the notification state in the database
+ /// Gets or sets whether to persist the notification state in the database.
///
- public bool PersistState { get; set; } = false;
+ public bool PersistState { get; set; } = true;
///
- /// Whether to enable logging for this notification
+ /// Gets or sets whether to enable logging for this notification.
///
- public bool EnableLogging { get; set; } = true;
+ public bool EnableLogging { get; set; }
///
- /// Optional action to handle logging (if null, logs to Debug output)
+ /// Gets or sets the optional action to handle logging.
///
public Action LogAction { get; set; }
///
- /// Optional action to execute when the notification is activated
+ /// Gets or sets the optional action to execute when the notification is activated.
///
public Action OnActivated { get; set; }
///
- /// Optional action to execute when the notification times out
+ /// Gets or sets the optional action to execute when the notification times out.
///
public Action OnTimeout { get; set; }
///
- /// Optional action to execute when an error occurs
+ /// Gets or sets the optional action to execute when an error occurs.
///
public Action OnError { get; set; }
///
- /// Optional serialized data for reminders
+ /// Gets or sets the optional deadline time for the notification.
///
- public string ReminderData { get; set; }
- }
-
- ///
- /// Represents a button on a notification
- ///
- public class NotificationButton
- {
- ///
- /// The text to display on the button
- ///
- public string Text { get; set; }
+ public DateTime? DeadlineTime { get; set; }
///
- /// Optional identifier for the button
+ /// Gets or sets the action to take when the deadline is reached.
///
- public string Id { get; set; } = Guid.NewGuid().ToString();
+ public DeadlineAction DeadlineAction { get; set; }
///
- /// Optional argument to pass when the button is clicked
+ /// Gets or sets whether to show a countdown timer on the notification.
///
- public string Argument { get; set; }
-
- ///
- /// Optional image URI for the button (file path or URL)
- ///
- public string ImageUri { get; set; }
-
- ///
- /// Whether this button should appear in the context menu
- ///
- public bool IsContextMenu { get; set; } = false;
-
- ///
- /// Optional tooltip text for the button
- ///
- public string ToolTip { get; set; }
-
- ///
- /// Optional background color for the button (hex format: #RRGGBB)
- ///
- public string BackgroundColor { get; set; }
-
- ///
- /// Optional text color for the button (hex format: #RRGGBB)
- ///
- public string TextColor { get; set; }
-
- ///
- /// Creates a new notification button with the specified text
- ///
- /// The text to display on the button
- /// Optional identifier for the button
- /// Optional argument to pass when the button is clicked
- public NotificationButton(string text, string id = null, string argument = null)
- {
- Text = text;
- if (!string.IsNullOrEmpty(id))
- Id = id;
- Argument = argument ?? Id;
- }
-
- ///
- /// Creates a new notification button with the specified text and image
- ///
- /// The text to display on the button
- /// The image URI for the button (file path or URL)
- /// Optional identifier for the button
- /// Optional argument to pass when the button is clicked
- public NotificationButton(string text, string imageUri, string id = null, string argument = null)
- : this(text, id, argument)
- {
- ImageUri = imageUri;
- }
+ public bool ShowCountdown { get; set; }
}
}
diff --git a/WindowsNotifications/Models/NotificationResult.cs b/WindowsNotifications/Models/NotificationResult.cs
index 32cca07..9e17bbf 100644
--- a/WindowsNotifications/Models/NotificationResult.cs
+++ b/WindowsNotifications/Models/NotificationResult.cs
@@ -3,127 +3,103 @@ using System;
namespace WindowsNotifications.Models
{
///
- /// Represents the result of a notification interaction
+ /// Represents the result of a notification interaction.
///
public class NotificationResult
{
///
- /// The unique identifier of the notification
+ /// Gets or sets the unique identifier of the notification.
///
public string NotificationId { get; set; }
///
- /// Whether the notification was successfully displayed
+ /// Gets or sets whether the notification was successfully displayed.
///
public bool Displayed { get; set; }
///
- /// Whether the notification was activated (clicked)
+ /// Gets or sets whether the notification was activated (clicked).
///
public bool Activated { get; set; }
///
- /// Whether the notification was dismissed
+ /// Gets or sets whether the notification was dismissed.
///
public bool Dismissed { get; set; }
///
- /// The ID of the button that was clicked, if any
+ /// Gets or sets the ID of the button that was clicked, if any.
///
public string ClickedButtonId { get; set; }
///
- /// The text of the button that was clicked, if any
+ /// Gets or sets the text of the button that was clicked, if any.
///
public string ClickedButtonText { get; set; }
///
- /// The argument of the button that was clicked, if any
+ /// Gets or sets the argument of the button that was clicked, if any.
///
public string ClickedButtonArgument { get; set; }
///
- /// The time when the notification was created
+ /// Gets or sets the time when the notification was created.
///
public DateTime CreatedTime { get; set; } = DateTime.Now;
///
- /// The time when the notification was interacted with, if any
+ /// Gets or sets the time when the notification was interacted with, if any.
///
public DateTime? InteractionTime { get; set; }
///
- /// Any error message that occurred during the notification process
+ /// Gets or sets any error message that occurred during the notification process.
///
public string ErrorMessage { get; set; }
///
- /// The error code, if an error occurred
+ /// Gets or sets the error code, if an error occurred.
///
public string ErrorCode { get; set; }
///
- /// Whether the notification was deferred
+ /// Gets or sets whether the notification was deferred.
///
public bool Deferred { get; set; }
///
- /// The time when the notification was deferred until, if applicable
+ /// Gets or sets the time when the notification was deferred until, if applicable.
///
public DateTime? DeferredUntil { get; set; }
///
- /// The reason for deferral, if applicable
+ /// Gets or sets the reason for deferral, if applicable.
///
public string DeferralReason { get; set; }
///
- /// The reason for dismissal, if applicable
+ /// Gets or sets the reason for dismissal, if applicable.
///
public string DismissalReason { get; set; }
///
- /// The system action that was taken (e.g., snooze, dismiss)
+ /// Gets or sets the system action that was taken (e.g., snooze, dismiss).
///
public string SystemAction { get; set; }
///
- /// Whether the deadline was reached
+ /// Gets or sets whether the deadline was reached.
///
public bool DeadlineReached { get; set; }
///
- /// The time when the deadline was reached, if applicable
+ /// Gets or sets the time when the deadline was reached, if applicable.
///
public DateTime? DeadlineReachedTime { get; set; }
///
- /// The action that was taken when the deadline was reached, if applicable
+ /// Gets or sets the action that was taken when the deadline was reached, if applicable.
///
public string DeadlineAction { get; set; }
-
- ///
- /// Creates a new notification result with the specified notification ID
- ///
- /// The unique identifier of the notification
- public NotificationResult(string notificationId)
- {
- NotificationId = notificationId;
- }
-
- ///
- /// Creates a new error notification result
- ///
- /// The unique identifier of the notification
- /// The error message
- /// A notification result with the error message
- public static NotificationResult Error(string notificationId, string errorMessage)
- {
- return new NotificationResult(notificationId)
- {
- ErrorMessage = errorMessage,
- Displayed = false
- };
- }
}
}
diff --git a/WindowsNotifications/Models/SimpleNotificationOptions.cs b/WindowsNotifications/Models/SimpleNotificationOptions.cs
deleted file mode 100644
index 46e6dee..0000000
--- a/WindowsNotifications/Models/SimpleNotificationOptions.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace WindowsNotifications.Models
-{
- ///
- /// Options for configuring a notification
- ///
- public class NotificationOptions
- {
- ///
- /// The title of the notification
- ///
- public string Title { get; set; }
-
- ///
- /// The main message body of the notification
- ///
- public string Message { get; set; }
-
- ///
- /// Optional logo image path
- ///
- public string LogoImagePath { get; set; }
-
- ///
- /// Optional hero image path
- ///
- public string HeroImagePath { get; set; }
-
- ///
- /// Optional attribution text
- ///
- public string Attribution { get; set; }
-
- ///
- /// Optional timeout in seconds. If set to 0, the notification will not timeout.
- ///
- public int TimeoutInSeconds { get; set; } = 0;
-
- ///
- /// Optional list of buttons to display on the notification
- ///
- public List Buttons { get; set; } = new List();
-
- ///
- /// Whether to run the notification asynchronously
- ///
- public bool Async { get; set; } = false;
-
- ///
- /// Optional unique identifier for the notification
- ///
- public string Id { get; set; } = Guid.NewGuid().ToString();
-
- ///
- /// Optional tag for grouping notifications
- ///
- public string Tag { get; set; }
-
- ///
- /// Optional group name for grouping notifications
- ///
- public string Group { get; set; }
-
- ///
- /// Whether to persist the notification state in the database
- ///
- public bool PersistState { get; set; } = false;
-
- ///
- /// Whether to enable logging for this notification
- ///
- public bool EnableLogging { get; set; } = true;
-
- ///
- /// Optional action to handle logging (if null, logs to Debug output)
- ///
- public Action LogAction { get; set; }
- }
-
- ///
- /// Represents a button on a notification
- ///
- public class NotificationButton
- {
- ///
- /// The text to display on the button
- ///
- public string Text { get; set; }
-
- ///
- /// Optional identifier for the button
- ///
- public string Id { get; set; } = Guid.NewGuid().ToString();
-
- ///
- /// Optional argument to pass when the button is clicked
- ///
- public string Argument { get; set; }
-
- ///
- /// Creates a new notification button with the specified text
- ///
- /// The text to display on the button
- /// Optional identifier for the button
- /// Optional argument to pass when the button is clicked
- public NotificationButton(string text, string id = null, string argument = null)
- {
- Text = text;
- if (!string.IsNullOrEmpty(id))
- Id = id;
- Argument = argument ?? Id;
- }
- }
-}
diff --git a/WindowsNotifications/Models/SimpleNotificationResult.cs b/WindowsNotifications/Models/SimpleNotificationResult.cs
deleted file mode 100644
index feea485..0000000
--- a/WindowsNotifications/Models/SimpleNotificationResult.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System;
-
-namespace WindowsNotifications.Models
-{
- ///
- /// Represents the result of a notification interaction
- ///
- public class NotificationResult
- {
- ///
- /// The unique identifier of the notification
- ///
- public string NotificationId { get; set; }
-
- ///
- /// Whether the notification was successfully displayed
- ///
- public bool Displayed { get; set; }
-
- ///
- /// Whether the notification was activated (clicked)
- ///
- public bool Activated { get; set; }
-
- ///
- /// Whether the notification was dismissed
- ///
- public bool Dismissed { get; set; }
-
- ///
- /// The ID of the button that was clicked, if any
- ///
- public string ClickedButtonId { get; set; }
-
- ///
- /// The text of the button that was clicked, if any
- ///
- public string ClickedButtonText { get; set; }
-
- ///
- /// The argument of the button that was clicked, if any
- ///
- public string ClickedButtonArgument { get; set; }
-
- ///
- /// The time when the notification was created
- ///
- public DateTime CreatedTime { get; set; } = DateTime.Now;
-
- ///
- /// The time when the notification was interacted with, if any
- ///
- public DateTime? InteractionTime { get; set; }
-
- ///
- /// Any error message that occurred during the notification process
- ///
- public string ErrorMessage { get; set; }
-
- ///
- /// Creates a new notification result with the specified notification ID
- ///
- /// The unique identifier of the notification
- public NotificationResult(string notificationId)
- {
- NotificationId = notificationId;
- }
-
- ///
- /// Creates a new error notification result
- ///
- /// The unique identifier of the notification
- /// The error message
- /// A notification result with the error message
- public static NotificationResult Error(string notificationId, string errorMessage)
- {
- return new NotificationResult(notificationId)
- {
- ErrorMessage = errorMessage,
- Displayed = false
- };
- }
- }
-}
diff --git a/WindowsNotifications/NotificationManager.cs b/WindowsNotifications/NotificationManager.cs
index a947c00..4067650 100644
--- a/WindowsNotifications/NotificationManager.cs
+++ b/WindowsNotifications/NotificationManager.cs
@@ -1,114 +1,118 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
-using System.Threading.Tasks;
using WindowsNotifications.Models;
using WindowsNotifications.Services;
namespace WindowsNotifications
{
///
- /// Main entry point for the Windows Notifications library
+ /// The main entry point for the Windows Notifications library.
///
public class NotificationManager
{
- private readonly UserSessionManager _sessionManager;
- private readonly ToastNotificationService _toastService;
+ private readonly string _databasePath;
private readonly DatabaseService _databaseService;
- private readonly Dictionary _reminderTimers = new Dictionary();
- private readonly Dictionary _deadlineTimers = new Dictionary();
- private readonly Timer _cleanupTimer;
+ private readonly ToastNotificationService _toastService;
+ private readonly UserSessionManager _sessionManager;
///
- /// Gets or sets the path to the LiteDB database file
+ /// Initializes a new instance of the class with the default database path.
///
- public string DatabasePath { get; private set; }
-
- ///
- /// Creates a new NotificationManager with the default database path
- ///
- public NotificationManager() : this(null)
+ public NotificationManager() : this(GetDefaultDatabasePath())
{
}
///
- /// Creates a new NotificationManager with the specified database path
+ /// Initializes a new instance of the class with the specified database path.
///
- /// The path to the LiteDB database file, or null to use the default
+ /// The path to the database file.
public NotificationManager(string databasePath)
{
- DatabasePath = databasePath;
- _sessionManager = new UserSessionManager();
+ _databasePath = databasePath;
+ _databaseService = new DatabaseService(_databasePath);
_toastService = new ToastNotificationService();
- _databaseService = new DatabaseService(databasePath);
-
- // Initialize cleanup timer to run every hour
- _cleanupTimer = new Timer(CleanupExpiredNotifications, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
+ _sessionManager = new UserSessionManager();
}
///
- /// Shows a notification with the specified options
+ /// Shows a notification with the specified options.
///
- /// The notification options
- /// The result of the notification
+ /// The notification options.
+ /// The notification result.
public NotificationResult ShowNotification(NotificationOptions options)
{
- // Validate options
if (options == null)
+ {
throw new ArgumentNullException(nameof(options));
+ }
- if (string.IsNullOrEmpty(options.Title) && string.IsNullOrEmpty(options.Message))
- throw new ArgumentException("Either Title or Message must be specified");
-
- // Get interactive user sessions
- var sessions = _sessionManager.GetInteractiveUserSessions();
- if (sessions.Count == 0)
- return NotificationResult.Error(options.Id, "No interactive user sessions found");
-
- // Show the notification in the first interactive session
- var session = sessions.First();
- NotificationResult result = null;
-
- using (var impersonation = new UserImpersonation())
+ // Generate a unique ID if not provided
+ if (string.IsNullOrEmpty(options.Id))
{
- try
+ options.Id = Guid.NewGuid().ToString();
+ }
+
+ // Log the notification
+ LogNotification(options, "Showing notification");
+
+ // Check if there's an interactive user session
+ if (!_sessionManager.HasInteractiveUserSession())
+ {
+ var result = new NotificationResult
{
- result = impersonation.ExecuteAsUser(session.SessionId, () => _toastService.ShowNotification(options));
- }
- catch (Exception ex)
+ NotificationId = options.Id,
+ Displayed = false,
+ ErrorMessage = "No interactive user session found",
+ CreatedTime = DateTime.Now
+ };
+
+ LogNotification(options, "No interactive user session found, notification not displayed");
+
+ if (options.PersistState)
{
- result = NotificationResult.Error(options.Id, $"Failed to show notification: {ex.Message}");
+ _databaseService.SaveNotificationResult(result);
}
+
+ return result;
}
- // Set up a reminder if requested
- if (options.ShowReminder && result.Displayed && !result.Activated && !result.Dismissed)
+ // Show the notification
+ NotificationResult notificationResult;
+ if (IsRunningAsSystem())
{
- SetupReminderTimer(options);
+ // Show notification using impersonation
+ notificationResult = _sessionManager.RunAsInteractiveUser(() => _toastService.ShowNotification(options));
}
-
- // Set up a deadline timer if specified
- if (options.DeadlineTime.HasValue && result.Displayed && !result.Activated && !result.Dismissed)
+ else
{
- SetupDeadlineTimer(options, result);
+ // Show notification directly
+ notificationResult = _toastService.ShowNotification(options);
}
- // Persist the result if requested
- if (options.PersistState && result != null)
+ // Save the result if persistence is enabled
+ if (options.PersistState)
{
- _databaseService.SaveNotificationResult(result);
+ _databaseService.SaveNotificationResult(notificationResult);
}
- return result;
+ // If not async, wait for the notification to complete
+ if (!options.Async)
+ {
+ notificationResult = WaitForNotification(options.Id);
+ }
+
+ return notificationResult;
}
///
- /// Shows a simple notification with the specified title and message
+ /// Shows a simple notification with the specified title and message.
///
- /// The title of the notification
- /// The message body of the notification
- /// The result of the notification
+ /// The title of the notification.
+ /// The message body of the notification.
+ /// The notification result.
public NotificationResult ShowSimpleNotification(string title, string message)
{
var options = new NotificationOptions
@@ -121,267 +125,173 @@ namespace WindowsNotifications
}
///
- /// Shows a notification with buttons
+ /// Shows a notification with buttons.
///
- /// The title of the notification
- /// The message body of the notification
- /// The buttons to display
- /// The result of the notification
+ /// The title of the notification.
+ /// The message body of the notification.
+ /// The buttons to display.
+ /// The notification result.
public NotificationResult ShowNotificationWithButtons(string title, string message, params string[] buttons)
{
var options = new NotificationOptions
{
Title = title,
- Message = message,
- Buttons = buttons.Select(b => new NotificationButton(b)).ToList()
+ Message = message
};
+ if (buttons != null && buttons.Length > 0)
+ {
+ for (int i = 0; i < buttons.Length; i++)
+ {
+ options.Buttons.Add(new NotificationButton(buttons[i], $"button{i}", buttons[i]));
+ }
+ }
+
return ShowNotification(options);
}
///
- /// Shows a reboot notification with deferral options
+ /// Shows a reboot notification with deferral options.
///
- /// The title of the notification
- /// The message body of the notification
- /// The text for the reboot button
- /// The text for the defer button
- /// The result of the notification
+ /// The title of the notification.
+ /// The message body of the notification.
+ /// The text for the reboot button.
+ /// The text for the defer button.
+ /// The notification result.
public NotificationResult ShowRebootNotification(string title, string message, string rebootButtonText = "Reboot Now", string deferButtonText = "Defer")
{
var options = new NotificationOptions
{
Title = title,
Message = message,
- Buttons = new List { new NotificationButton(rebootButtonText, "reboot") },
- DeferralOptions = new DeferralOptions { DeferButtonText = deferButtonText },
- PersistState = true
+ DeferralOptions = new DeferralOptions
+ {
+ Enabled = true,
+ DeferButtonText = deferButtonText
+ }
};
+ options.Buttons.Add(new NotificationButton(rebootButtonText, "reboot", "reboot"));
+
return ShowNotification(options);
}
///
- /// Gets the result of a notification
+ /// Gets the result of a notification.
///
- /// The ID of the notification
- /// The notification result, or null if not found
+ /// The unique identifier of the notification.
+ /// The notification result, or null if not found.
public NotificationResult GetNotificationResult(string notificationId)
{
- // Try to get from the toast service first
- var result = _toastService.GetNotificationResult(notificationId);
+ return _databaseService.GetNotificationResult(notificationId);
+ }
- // If not found, try to get from the database
- if (result == null)
- result = _databaseService.GetNotificationResult(notificationId);
+ ///
+ /// Waits for a notification to complete.
+ ///
+ /// The unique identifier of the notification.
+ /// The timeout in milliseconds, or -1 to wait indefinitely.
+ /// The notification result.
+ public NotificationResult WaitForNotification(string notificationId, int timeout = -1)
+ {
+ DateTime startTime = DateTime.Now;
+ NotificationResult result = null;
+
+ while (true)
+ {
+ result = GetNotificationResult(notificationId);
+
+ if (result != null && (result.Activated || result.Dismissed || result.DeadlineReached || !string.IsNullOrEmpty(result.ErrorMessage)))
+ {
+ break;
+ }
+
+ if (timeout > 0 && (DateTime.Now - startTime).TotalMilliseconds > timeout)
+ {
+ break;
+ }
+
+ Thread.Sleep(500);
+ }
return result;
}
///
- /// Waits for a notification to complete
+ /// Gets all notification results from the database.
///
- /// The ID of the notification
- /// The timeout in milliseconds, or -1 to wait indefinitely
- /// The notification result, or null if timed out or not found
- public NotificationResult WaitForNotification(string notificationId, int timeout = -1)
- {
- return _toastService.WaitForNotification(notificationId, timeout);
- }
-
- ///
- /// Gets all notification results from the database
- ///
- /// A list of notification results
+ /// A list of notification results.
public List GetAllNotificationResults()
{
return _databaseService.GetAllNotificationResults();
}
///
- /// Deletes a notification result from the database
+ /// Deletes a notification result from the database.
///
- /// The ID of the notification
- /// True if the deletion was successful, false otherwise
+ /// The unique identifier of the notification.
+ /// True if the notification was deleted, false otherwise.
public bool DeleteNotificationResult(string notificationId)
{
return _databaseService.DeleteNotificationResult(notificationId);
}
///
- /// Deletes all notification results from the database
+ /// Deletes all notification results from the database.
///
- /// True if the deletion was successful, false otherwise
+ /// True if all notifications were deleted, false otherwise.
public bool DeleteAllNotificationResults()
{
return _databaseService.DeleteAllNotificationResults();
}
///
- /// Gets the path to the database file
+ /// Gets the path to the database file.
///
- /// The database file path
+ /// The path to the database file.
public string GetDatabaseFilePath()
{
- return _databaseService.GetDatabaseFilePath();
+ return _databasePath;
}
///
- /// Sets up a reminder timer for a notification
+ /// Checks if the current process is running as SYSTEM.
///
- /// The notification options
- private void SetupReminderTimer(NotificationOptions options)
- {
- // Create a reminder timer
- var timer = new Timer(state =>
- {
- var reminderOptions = new NotificationOptions
- {
- Title = $"Reminder: {options.Title}",
- Message = options.Message,
- Buttons = options.Buttons,
- Async = options.Async,
- Tag = options.Tag,
- Group = options.Group,
- LogoImagePath = options.LogoImagePath,
- HeroImagePath = options.HeroImagePath,
- Attribution = options.Attribution,
- TimeoutInSeconds = options.TimeoutInSeconds,
- PersistState = options.PersistState
- };
-
- ShowNotification(reminderOptions);
- }, null, options.ReminderTimeInMinutes * 60 * 1000, Timeout.Infinite);
-
- // Store the timer
- _reminderTimers[options.Id] = timer;
- }
-
- ///
- /// Sets up a deadline timer for a notification
- ///
- /// The notification options
- /// The notification result
- private void SetupDeadlineTimer(NotificationOptions options, NotificationResult result)
- {
- if (!options.DeadlineTime.HasValue)
- return;
-
- // Calculate time until deadline
- TimeSpan timeUntilDeadline = options.DeadlineTime.Value - DateTime.Now;
- if (timeUntilDeadline.TotalMilliseconds <= 0)
- return; // Deadline already passed
-
- // Create a deadline timer
- var timer = new Timer(state =>
- {
- try
- {
- // Get the latest result from the database if persisted
- NotificationResult currentResult = options.PersistState
- ? _databaseService.GetNotificationResult(options.Id) ?? result
- : result;
-
- // If the notification has already been interacted with, do nothing
- if (currentResult.Activated || currentResult.Dismissed)
- return;
-
- // Update the result
- currentResult.DeadlineReached = true;
- currentResult.DeadlineReachedTime = DateTime.Now;
- currentResult.DeadlineAction = options.DeadlineAction?.ActionType.ToString() ?? "None";
-
- // Execute the deadline action if specified
- if (options.DeadlineAction != null && options.DeadlineAction.ActionType != DeadlineActionType.None)
- {
- options.DeadlineAction.Execute(currentResult);
- }
-
- // Persist the updated result if requested
- if (options.PersistState)
- {
- _databaseService.SaveNotificationResult(currentResult);
- }
-
- // Log the deadline reached event
- if (options.EnableLogging && options.LogAction != null)
- {
- string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [DeadlineReached] ID: {options.Id}, Title: {options.Title}, Action: {currentResult.DeadlineAction}";
- options.LogAction(logEntry);
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error executing deadline action: {ex.Message}");
- }
- }, null, (int)timeUntilDeadline.TotalMilliseconds, Timeout.Infinite);
-
- // Store the timer
- _deadlineTimers[options.Id] = timer;
- }
-
- ///
- /// Cleans up expired notifications from the database
- ///
- private void CleanupExpiredNotifications(object state)
- {
- try
- {
- // Get all notification results from the database
- var results = _databaseService.GetAllNotificationResults();
- if (results == null || results.Count == 0)
- return;
-
- // Find expired notifications (older than 30 days)
- var expiredResults = results.Where(r => r.CreatedTime < DateTime.Now.AddDays(-30)).ToList();
- if (expiredResults.Count == 0)
- return;
-
- // Delete expired notifications
- foreach (var result in expiredResults)
- {
- _databaseService.DeleteNotificationResult(result.NotificationId);
-
- // Clean up any associated timers
- if (_reminderTimers.ContainsKey(result.NotificationId))
- {
- _reminderTimers[result.NotificationId].Dispose();
- _reminderTimers.Remove(result.NotificationId);
- }
-
- if (_deadlineTimers.ContainsKey(result.NotificationId))
- {
- _deadlineTimers[result.NotificationId].Dispose();
- _deadlineTimers.Remove(result.NotificationId);
- }
- }
-
- System.Diagnostics.Debug.WriteLine($"Cleaned up {expiredResults.Count} expired notifications");
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error cleaning up expired notifications: {ex.Message}");
- }
- }
-
- ///
- /// Checks if the current process is running as SYSTEM
- ///
- /// True if running as SYSTEM, false otherwise
+ /// True if running as SYSTEM, false otherwise.
public bool IsRunningAsSystem()
{
- return UserImpersonation.IsRunningAsSystem;
+ return _sessionManager.IsRunningAsSystem();
}
///
- /// Gets all interactive user sessions
+ /// Gets all interactive user sessions.
///
- /// A list of interactive user sessions
+ /// A list of interactive user sessions.
public List GetInteractiveUserSessions()
{
- return _sessionManager.GetInteractiveUserSessions()
- .Select(s => s.ToString())
- .ToList();
+ return _sessionManager.GetInteractiveUserSessions();
+ }
+
+ private static string GetDefaultDatabasePath()
+ {
+ string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
+ string dbDirectory = Path.Combine(appDataPath, "WindowsNotifications");
+
+ if (!Directory.Exists(dbDirectory))
+ {
+ Directory.CreateDirectory(dbDirectory);
+ }
+
+ return Path.Combine(dbDirectory, "notifications.db");
+ }
+
+ private void LogNotification(NotificationOptions options, string message)
+ {
+ if (options.EnableLogging)
+ {
+ string logMessage = $"[{DateTime.Now}] [{options.Id}] {message}";
+ options.LogAction?.Invoke(logMessage);
+ }
}
}
}
diff --git a/WindowsNotifications/Properties/AssemblyInfo.cs b/WindowsNotifications/Properties/AssemblyInfo.cs
index ce253d2..86ad166 100644
--- a/WindowsNotifications/Properties/AssemblyInfo.cs
+++ b/WindowsNotifications/Properties/AssemblyInfo.cs
@@ -6,11 +6,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("WindowsNotifications")]
-[assembly: AssemblyDescription("Windows Notification Library for PowerShell")]
+[assembly: AssemblyDescription("A library for displaying Windows notifications from SYSTEM context")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WindowsNotifications")]
-[assembly: AssemblyCopyright("Copyright © 2024")]
+[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/WindowsNotifications/Resources/LiteDB.dll b/WindowsNotifications/Resources/LiteDB.dll
index 213eeec..14798bb 100644
Binary files a/WindowsNotifications/Resources/LiteDB.dll and b/WindowsNotifications/Resources/LiteDB.dll differ
diff --git a/WindowsNotifications/Services/DatabaseService.cs b/WindowsNotifications/Services/DatabaseService.cs
index 7b638ab..3a5e6d1 100644
--- a/WindowsNotifications/Services/DatabaseService.cs
+++ b/WindowsNotifications/Services/DatabaseService.cs
@@ -1,64 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Reflection;
+using System.Linq;
using WindowsNotifications.Models;
using WindowsNotifications.Utils;
namespace WindowsNotifications.Services
{
///
- /// Service for managing notification state persistence using LiteDB
+ /// Service for managing notification data in LiteDB.
///
internal class DatabaseService
{
- private readonly string _dbPath;
- private readonly LiteDBEmbedded _liteDb;
- private const string DEFAULT_DB_FOLDER = "%PROGRAMDATA%\\WindowsNotifications";
- private const string DEFAULT_DB_NAME = "notifications.db";
+ private readonly string _databasePath;
///
- /// Creates a new DatabaseService with the specified database path
+ /// Initializes a new instance of the class.
///
- /// The path to the database file, or null to use the default
- public DatabaseService(string dbPath = null)
+ /// The path to the database file.
+ public DatabaseService(string databasePath)
{
- // Determine the database path
- _dbPath = GetDatabasePath(dbPath);
-
- // Ensure the directory exists
- string directory = Path.GetDirectoryName(_dbPath);
- if (!Directory.Exists(directory))
- Directory.CreateDirectory(directory);
-
- // Initialize LiteDB
- _liteDb = new LiteDBEmbedded(_dbPath);
+ _databasePath = databasePath;
+ EnsureDatabaseDirectoryExists();
}
///
- /// Gets the database path, using the default if none is specified
+ /// Saves a notification result to the database.
///
- /// The specified database path, or null to use the default
- /// The resolved database path
- private string GetDatabasePath(string dbPath)
- {
- if (!string.IsNullOrEmpty(dbPath))
- return dbPath;
-
- string folder = Environment.ExpandEnvironmentVariables(DEFAULT_DB_FOLDER);
- return Path.Combine(folder, DEFAULT_DB_NAME);
- }
-
- ///
- /// Saves a notification result to the database
- ///
- /// The notification result to save
- /// True if the save was successful, false otherwise
+ /// The notification result to save.
+ /// True if the result was saved successfully, false otherwise.
public bool SaveNotificationResult(NotificationResult result)
{
try
{
- return _liteDb.UpsertNotificationResult(result);
+ using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
+ {
+ var collection = db.GetCollection("notifications");
+
+ // Create index on NotificationId
+ collection.EnsureIndex(x => x.NotificationId);
+
+ // Check if the notification already exists
+ var existingResult = collection.FindOne(x => x.NotificationId == result.NotificationId);
+
+ if (existingResult != null)
+ {
+ // Update existing notification
+ collection.Update(result);
+ }
+ else
+ {
+ // Insert new notification
+ collection.Insert(result);
+ }
+
+ return true;
+ }
}
catch (Exception)
{
@@ -67,15 +64,19 @@ namespace WindowsNotifications.Services
}
///
- /// Gets a notification result from the database
+ /// Gets a notification result from the database.
///
- /// The ID of the notification
- /// The notification result, or null if not found
+ /// The unique identifier of the notification.
+ /// The notification result, or null if not found.
public NotificationResult GetNotificationResult(string notificationId)
{
try
{
- return _liteDb.GetNotificationResult(notificationId);
+ using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
+ {
+ var collection = db.GetCollection("notifications");
+ return collection.FindOne(x => x.NotificationId == notificationId);
+ }
}
catch (Exception)
{
@@ -84,14 +85,18 @@ namespace WindowsNotifications.Services
}
///
- /// Gets all notification results from the database
+ /// Gets all notification results from the database.
///
- /// A list of notification results
+ /// A list of notification results.
public List GetAllNotificationResults()
{
try
{
- return _liteDb.GetAllNotificationResults();
+ using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
+ {
+ var collection = db.GetCollection("notifications");
+ return collection.FindAll().ToList();
+ }
}
catch (Exception)
{
@@ -100,15 +105,19 @@ namespace WindowsNotifications.Services
}
///
- /// Deletes a notification result from the database
+ /// Deletes a notification result from the database.
///
- /// The ID of the notification
- /// True if the deletion was successful, false otherwise
+ /// The unique identifier of the notification.
+ /// True if the notification was deleted, false otherwise.
public bool DeleteNotificationResult(string notificationId)
{
try
{
- return _liteDb.DeleteNotificationResult(notificationId);
+ using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
+ {
+ var collection = db.GetCollection("notifications");
+ return collection.Delete(x => x.NotificationId == notificationId) > 0;
+ }
}
catch (Exception)
{
@@ -117,14 +126,19 @@ namespace WindowsNotifications.Services
}
///
- /// Deletes all notification results from the database
+ /// Deletes all notification results from the database.
///
- /// True if the deletion was successful, false otherwise
+ /// True if all notifications were deleted, false otherwise.
public bool DeleteAllNotificationResults()
{
try
{
- return _liteDb.DeleteAllNotificationResults();
+ using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
+ {
+ var collection = db.GetCollection("notifications");
+ collection.DeleteAll();
+ return true;
+ }
}
catch (Exception)
{
@@ -132,13 +146,13 @@ namespace WindowsNotifications.Services
}
}
- ///
- /// Gets the path to the database file
- ///
- /// The database file path
- public string GetDatabaseFilePath()
+ private void EnsureDatabaseDirectoryExists()
{
- return _dbPath;
+ string directory = Path.GetDirectoryName(_databasePath);
+ if (!Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
}
}
}
diff --git a/WindowsNotifications/Services/ToastNotificationService.cs b/WindowsNotifications/Services/ToastNotificationService.cs
index a5347fa..48ebbf7 100644
--- a/WindowsNotifications/Services/ToastNotificationService.cs
+++ b/WindowsNotifications/Services/ToastNotificationService.cs
@@ -1,370 +1,319 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
+using System.Text;
using System.Threading;
-using System.Threading.Tasks;
using System.Windows;
-using Windows.Data.Xml.Dom;
-using Windows.UI.Notifications;
+using System.Windows.Forms;
+using System.Xml;
using WindowsNotifications.Models;
namespace WindowsNotifications.Services
{
///
- /// Service for creating and displaying Windows Toast Notifications
+ /// Service for displaying toast notifications.
///
internal class ToastNotificationService
{
- private const string APP_ID = "WindowsNotifications.PowerShell";
- private readonly Dictionary _notificationEvents = new Dictionary();
- private readonly Dictionary _notificationResults = new Dictionary();
-
///
- /// Shows a toast notification with the specified options
+ /// Shows a notification with the specified options.
///
- /// The notification options
- /// The result of the notification
+ /// The notification options.
+ /// The notification result.
public NotificationResult ShowNotification(NotificationOptions options)
{
- var result = new NotificationResult(options.Id);
+ var result = new NotificationResult
+ {
+ NotificationId = options.Id,
+ CreatedTime = DateTime.Now
+ };
try
{
- // Register COM server for notifications
- RegisterComServer();
-
// Create the toast XML
- string toastXml = CreateToastXml(options);
- XmlDocument xmlDoc = new XmlDocument();
- xmlDoc.LoadXml(toastXml);
+ string toastXml = GenerateToastXml(options);
- // Create the toast notification
- ToastNotification toast = new ToastNotification(xmlDoc);
+ // Create a temporary file for the toast XML
+ string tempFile = Path.Combine(Path.GetTempPath(), $"toast_{options.Id}.xml");
+ File.WriteAllText(tempFile, toastXml);
- // Set up event handlers
- var resetEvent = new ManualResetEvent(false);
- _notificationEvents[options.Id] = resetEvent;
- _notificationResults[options.Id] = result;
+ // Create a temporary file for the result
+ string resultFile = Path.Combine(Path.GetTempPath(), $"result_{options.Id}.json");
- // Register event handlers
- toast.Activated += (sender, args) => OnToastActivated(sender, args, options);
- toast.Dismissed += (sender, args) => OnToastDismissed(sender, args, options);
- toast.Failed += (sender, args) => OnToastFailed(sender, args, options);
+ // Create the process arguments
+ string arguments = $"-ToastFile \"{tempFile}\" -ResultFile \"{resultFile}\"";
- // Show the notification
- ToastNotificationManager.CreateToastNotifier(APP_ID).Show(toast);
- result.Displayed = true;
-
- // If not async, wait for the notification to be interacted with
- if (!options.Async)
+ if (options.TimeoutInSeconds > 0)
{
- resetEvent.WaitOne();
- result = _notificationResults[options.Id];
+ arguments += $" -Timeout {options.TimeoutInSeconds}";
}
- return result;
+ if (options.DeadlineTime.HasValue)
+ {
+ arguments += $" -Deadline \"{options.DeadlineTime.Value:yyyy-MM-dd HH:mm:ss}\"";
+ }
+
+ if (options.ShowCountdown)
+ {
+ arguments += " -ShowCountdown";
+ }
+
+ // Launch the toast process
+ System.Diagnostics.Process process = new System.Diagnostics.Process();
+ process.StartInfo.FileName = "powershell.exe";
+ process.StartInfo.Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"& {{ Import-Module Microsoft.PowerShell.Management; [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null; $xml = [Windows.Data.Xml.Dom.XmlDocument]::new(); $xml.LoadXml('{toastXml}'); $toast = [Windows.UI.Notifications.ToastNotification]::new($xml); $notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('WindowsNotifications'); $notifier.Show($toast); }}\"";
+ process.StartInfo.UseShellExecute = false;
+ process.StartInfo.CreateNoWindow = true;
+ process.Start();
+
+ result.Displayed = true;
+
+ // If not async, wait for the process to complete
+ if (!options.Async)
+ {
+ process.WaitForExit();
+
+ // Wait for the result file to be created
+ int maxWaitTime = options.TimeoutInSeconds > 0 ? options.TimeoutInSeconds * 1000 : 30000;
+ int waitTime = 0;
+ int sleepTime = 100;
+
+ while (!File.Exists(resultFile) && waitTime < maxWaitTime)
+ {
+ Thread.Sleep(sleepTime);
+ waitTime += sleepTime;
+ }
+
+ // Read the result file if it exists
+ if (File.Exists(resultFile))
+ {
+ string resultJson = File.ReadAllText(resultFile);
+ ParseResultJson(resultJson, result);
+
+ // Delete the result file
+ try
+ {
+ File.Delete(resultFile);
+ }
+ catch { }
+ }
+ }
+
+ // Clean up the temporary file
+ try
+ {
+ File.Delete(tempFile);
+ }
+ catch { }
}
catch (Exception ex)
{
- return NotificationResult.Error(options.Id, ex.Message);
+ result.Displayed = false;
+ result.ErrorMessage = ex.Message;
}
+
+ return result;
}
- ///
- /// Gets the result of a notification
- ///
- /// The ID of the notification
- /// The notification result, or null if not found
- public NotificationResult GetNotificationResult(string notificationId)
+ private string GenerateToastXml(NotificationOptions options)
{
- if (_notificationResults.ContainsKey(notificationId))
- return _notificationResults[notificationId];
-
- return null;
- }
-
- ///
- /// Waits for a notification to complete
- ///
- /// The ID of the notification
- /// The timeout in milliseconds, or -1 to wait indefinitely
- /// The notification result, or null if timed out or not found
- public NotificationResult WaitForNotification(string notificationId, int timeout = -1)
- {
- if (!_notificationEvents.ContainsKey(notificationId))
- return null;
-
- var resetEvent = _notificationEvents[notificationId];
- if (resetEvent.WaitOne(timeout))
- return _notificationResults[notificationId];
-
- return null;
- }
-
- ///
- /// Creates the XML for a toast notification
- ///
- /// The notification options
- /// The toast XML
- private string CreateToastXml(NotificationOptions options)
- {
- // Create the XML document
- XmlDocument doc = new XmlDocument();
-
- // Create the toast element
- XmlElement toastElement = doc.CreateElement("toast");
- doc.AppendChild(toastElement);
-
- // Add optional attributes
- if (!string.IsNullOrEmpty(options.Tag))
- toastElement.SetAttribute("tag", options.Tag);
-
- if (!string.IsNullOrEmpty(options.Group))
- toastElement.SetAttribute("group", options.Group);
-
- if (options.TimeoutInSeconds > 0)
- toastElement.SetAttribute("duration", options.TimeoutInSeconds <= 30 ? "short" : "long");
-
- // Add scenario if specified
- if (!string.IsNullOrEmpty(options.Scenario))
- toastElement.SetAttribute("scenario", options.Scenario);
-
- // Add launch attribute if specified
- if (!string.IsNullOrEmpty(options.LaunchArgument))
- toastElement.SetAttribute("launch", options.LaunchArgument);
-
- // Create the visual element
- XmlElement visualElement = doc.CreateElement("visual");
- toastElement.AppendChild(visualElement);
-
- // Add branding color if specified
- if (!string.IsNullOrEmpty(options.BrandingColor))
- visualElement.SetAttribute("baseUri", options.BrandingColor);
-
- // Add accent color if specified
- if (!string.IsNullOrEmpty(options.AccentColor))
- visualElement.SetAttribute("addImageQuery", options.AccentColor);
-
- // Create the binding element
- XmlElement bindingElement = doc.CreateElement("binding");
- bindingElement.SetAttribute("template", "ToastGeneric");
- visualElement.AppendChild(bindingElement);
-
- // Add dark theme if specified
- if (options.UseDarkTheme)
- bindingElement.SetAttribute("hint-darkTheme", "true");
-
- // Add custom font family if specified
- if (!string.IsNullOrEmpty(options.FontFamily))
- bindingElement.SetAttribute("hint-textStacking", options.FontFamily);
-
- // Add title and message
- if (!string.IsNullOrEmpty(options.Title))
- {
- XmlElement titleElement = doc.CreateElement("text");
- titleElement.InnerText = options.Title;
- bindingElement.AppendChild(titleElement);
- }
-
- if (!string.IsNullOrEmpty(options.Message))
- {
- XmlElement messageElement = doc.CreateElement("text");
- messageElement.InnerText = options.Message;
- bindingElement.AppendChild(messageElement);
- }
-
- // Add countdown timer if enabled
- if (options.ShowCountdown && options.DeadlineTime.HasValue)
- {
- TimeSpan timeLeft = options.DeadlineTime.Value - DateTime.Now;
- if (timeLeft.TotalSeconds > 0)
- {
- string countdownText = $"Time remaining: {(int)timeLeft.TotalHours}h {timeLeft.Minutes}m {timeLeft.Seconds}s";
- XmlElement countdownElement = doc.CreateElement("text");
- countdownElement.InnerText = countdownText;
- bindingElement.AppendChild(countdownElement);
- }
- }
-
- // Add images if specified
- // Logo image (small app logo)
+ StringBuilder xml = new StringBuilder();
+ xml.AppendLine("");
+ xml.AppendLine(" ");
+ xml.AppendLine(" ");
+
+ // Title
+ xml.AppendLine($" {EscapeXml(options.Title)}");
+
+ // Message
+ xml.AppendLine($" {EscapeXml(options.Message)}");
+
+ // Logo image
if (!string.IsNullOrEmpty(options.LogoImagePath))
{
- bool isUrl = options.LogoImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase);
- if (isUrl || File.Exists(options.LogoImagePath))
- {
- XmlElement logoElement = doc.CreateElement("image");
- logoElement.SetAttribute("placement", "appLogoOverride");
- logoElement.SetAttribute("src", options.LogoImagePath);
- logoElement.SetAttribute("hint-crop", "circle");
- bindingElement.AppendChild(logoElement);
- }
+ xml.AppendLine($" ");
}
-
- // Hero image (large banner image)
+
+ // Hero image
if (!string.IsNullOrEmpty(options.HeroImagePath))
{
- bool isUrl = options.HeroImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase);
- if (isUrl || File.Exists(options.HeroImagePath))
- {
- XmlElement heroElement = doc.CreateElement("image");
- heroElement.SetAttribute("placement", "hero");
- heroElement.SetAttribute("src", options.HeroImagePath);
- bindingElement.AppendChild(heroElement);
- }
+ xml.AppendLine($" ");
}
-
- // Inline image (image within the notification content)
- if (!string.IsNullOrEmpty(options.InlineImagePath))
- {
- bool isUrl = options.InlineImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase);
- if (isUrl || File.Exists(options.InlineImagePath))
- {
- XmlElement inlineElement = doc.CreateElement("image");
- inlineElement.SetAttribute("src", options.InlineImagePath);
- bindingElement.AppendChild(inlineElement);
- }
- }
-
- // App icon (icon displayed in the notification)
- if (!string.IsNullOrEmpty(options.AppIconPath))
- {
- bool isUrl = options.AppIconPath.StartsWith("http", StringComparison.OrdinalIgnoreCase);
- if (isUrl || File.Exists(options.AppIconPath))
- {
- XmlElement appIconElement = doc.CreateElement("image");
- appIconElement.SetAttribute("placement", "appIcon");
- appIconElement.SetAttribute("src", options.AppIconPath);
- bindingElement.AppendChild(appIconElement);
- }
- }
-
- // Background image (background of the entire toast)
- if (!string.IsNullOrEmpty(options.BackgroundImagePath))
- {
- bool isUrl = options.BackgroundImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase);
- if (isUrl || File.Exists(options.BackgroundImagePath))
- {
- XmlElement backgroundElement = doc.CreateElement("image");
- backgroundElement.SetAttribute("placement", "background");
- backgroundElement.SetAttribute("src", options.BackgroundImagePath);
- bindingElement.AppendChild(backgroundElement);
- }
- }
-
- // Add attribution if specified
+
+ // Attribution
if (!string.IsNullOrEmpty(options.Attribution))
{
- XmlElement attributionElement = doc.CreateElement("text");
- attributionElement.SetAttribute("placement", "attribution");
- attributionElement.InnerText = options.Attribution;
- bindingElement.AppendChild(attributionElement);
+ xml.AppendLine($" {EscapeXml(options.Attribution)}");
}
-
- // Add branding text if specified
- if (!string.IsNullOrEmpty(options.BrandingText))
- {
- XmlElement brandingElement = doc.CreateElement("text");
- brandingElement.SetAttribute("placement", "attribution");
- brandingElement.InnerText = options.BrandingText;
- bindingElement.AppendChild(brandingElement);
- }
-
- // Add progress bar if specified
- if (options.ShowProgressBar && options.ProgressValue >= 0 && options.ProgressValue <= 100)
- {
- XmlElement progressElement = doc.CreateElement("progress");
- progressElement.SetAttribute("value", (options.ProgressValue / 100.0).ToString("0.00"));
- progressElement.SetAttribute("status", options.ProgressStatus ?? "Progress");
- bindingElement.AppendChild(progressElement);
- }
-
- // Add actions if there are buttons or deferrals
+
+ xml.AppendLine(" ");
+ xml.AppendLine(" ");
+
+ // Actions
if (options.Buttons.Count > 0 || (options.DeferralOptions != null && options.DeferralOptions.Enabled))
{
- XmlElement actionsElement = doc.CreateElement("actions");
- toastElement.AppendChild(actionsElement);
-
- // Add deferral button if enabled
- if (options.DeferralOptions != null && options.DeferralOptions.Enabled)
- {
- // Create the input element for deferral selection
- XmlElement inputElement = doc.CreateElement("input");
- inputElement.SetAttribute("id", "deferralSelect");
- inputElement.SetAttribute("type", "selection");
- inputElement.SetAttribute("title", options.DeferralOptions.DeferralPrompt);
- actionsElement.AppendChild(inputElement);
-
- // Add deferral options
- foreach (var deferOption in options.DeferralOptions.DeferralChoices)
- {
- XmlElement selectionElement = doc.CreateElement("selection");
- selectionElement.SetAttribute("id", deferOption.Id);
- selectionElement.SetAttribute("content", deferOption.Text);
- inputElement.AppendChild(selectionElement);
- }
-
- // Add the defer action button
- XmlElement deferActionElement = doc.CreateElement("action");
- deferActionElement.SetAttribute("activationType", "system");
- deferActionElement.SetAttribute("arguments", "defer");
- deferActionElement.SetAttribute("content", options.DeferralOptions.DeferButtonText);
- deferActionElement.SetAttribute("hint-inputId", "deferralSelect");
- actionsElement.AppendChild(deferActionElement);
- }
-
- // Add regular buttons
+ xml.AppendLine(" ");
+
+ // Regular buttons
foreach (var button in options.Buttons)
{
- XmlElement actionElement = doc.CreateElement("action");
- actionElement.SetAttribute("activationType", "foreground");
- actionElement.SetAttribute("arguments", button.Id);
- actionElement.SetAttribute("content", button.Text);
-
- // Add optional button attributes
- if (!string.IsNullOrEmpty(button.ImageUri))
- actionElement.SetAttribute("imageUri", button.ImageUri);
-
- if (button.IsContextMenu)
- actionElement.SetAttribute("placement", "contextMenu");
-
- actionsElement.AppendChild(actionElement);
+ xml.AppendLine($" ");
}
- }
-
- // Add audio element if specified
- if (!string.IsNullOrEmpty(options.AudioSource) || options.SilentMode)
- {
- XmlElement audioElement = doc.CreateElement("audio");
-
- if (options.SilentMode)
+
+ // Deferral options
+ if (options.DeferralOptions != null && options.DeferralOptions.Enabled)
{
- audioElement.SetAttribute("silent", "true");
+ xml.AppendLine($" ");
+
+ if (options.DeferralOptions.DeferralOptions.Count > 0)
+ {
+ xml.AppendLine($" ");
+
+ foreach (var deferOption in options.DeferralOptions.DeferralOptions)
+ {
+ string text = deferOption >= 60
+ ? string.Format(options.DeferralOptions.DeferralOptionHourFormat, deferOption / 60)
+ : string.Format(options.DeferralOptions.DeferralOptionFormat, deferOption);
+
+ xml.AppendLine($" ");
+ }
+
+ xml.AppendLine(" ");
+ }
}
- else if (!string.IsNullOrEmpty(options.AudioSource))
- {
- audioElement.SetAttribute("src", options.AudioSource);
-
- if (options.LoopAudio)
- audioElement.SetAttribute("loop", "true");
- }
-
- toastElement.AppendChild(audioElement);
+
+ xml.AppendLine(" ");
+ }
+
+ xml.AppendLine("");
+
+ return xml.ToString();
+ }
+
+ private void ParseResultJson(string json, NotificationResult result)
+ {
+ try
+ {
+ // Simple JSON parsing without dependencies
+ if (json.Contains("\"Activated\":true"))
+ {
+ result.Activated = true;
+ }
+
+ if (json.Contains("\"Dismissed\":true"))
+ {
+ result.Dismissed = true;
+ }
+
+ // Extract button ID
+ int buttonIdStart = json.IndexOf("\"ClickedButtonId\":\"");
+ if (buttonIdStart >= 0)
+ {
+ buttonIdStart += "\"ClickedButtonId\":\"".Length;
+ int buttonIdEnd = json.IndexOf("\"", buttonIdStart);
+ if (buttonIdEnd > buttonIdStart)
+ {
+ result.ClickedButtonId = json.Substring(buttonIdStart, buttonIdEnd - buttonIdStart);
+ }
+ }
+
+ // Extract button text
+ int buttonTextStart = json.IndexOf("\"ClickedButtonText\":\"");
+ if (buttonTextStart >= 0)
+ {
+ buttonTextStart += "\"ClickedButtonText\":\"".Length;
+ int buttonTextEnd = json.IndexOf("\"", buttonTextStart);
+ if (buttonTextEnd > buttonTextStart)
+ {
+ result.ClickedButtonText = json.Substring(buttonTextStart, buttonTextEnd - buttonTextStart);
+ }
+ }
+
+ // Extract button argument
+ int buttonArgStart = json.IndexOf("\"ClickedButtonArgument\":\"");
+ if (buttonArgStart >= 0)
+ {
+ buttonArgStart += "\"ClickedButtonArgument\":\"".Length;
+ int buttonArgEnd = json.IndexOf("\"", buttonArgStart);
+ if (buttonArgEnd > buttonArgStart)
+ {
+ result.ClickedButtonArgument = json.Substring(buttonArgStart, buttonArgEnd - buttonArgStart);
+ }
+ }
+
+ // Extract interaction time
+ int interactionTimeStart = json.IndexOf("\"InteractionTime\":\"");
+ if (interactionTimeStart >= 0)
+ {
+ interactionTimeStart += "\"InteractionTime\":\"".Length;
+ int interactionTimeEnd = json.IndexOf("\"", interactionTimeStart);
+ if (interactionTimeEnd > interactionTimeStart)
+ {
+ string timeStr = json.Substring(interactionTimeStart, interactionTimeEnd - interactionTimeStart);
+ if (DateTime.TryParse(timeStr, out DateTime interactionTime))
+ {
+ result.InteractionTime = interactionTime;
+ }
+ }
+ }
+
+ // Check for deferral
+ if (result.ClickedButtonId == "defer")
+ {
+ result.Deferred = true;
+
+ // Extract deferral time
+ int deferTimeStart = json.IndexOf("\"DeferredUntil\":\"");
+ if (deferTimeStart >= 0)
+ {
+ deferTimeStart += "\"DeferredUntil\":\"".Length;
+ int deferTimeEnd = json.IndexOf("\"", deferTimeStart);
+ if (deferTimeEnd > deferTimeStart)
+ {
+ string timeStr = json.Substring(deferTimeStart, deferTimeEnd - deferTimeStart);
+ if (DateTime.TryParse(timeStr, out DateTime deferredUntil))
+ {
+ result.DeferredUntil = deferredUntil;
+ }
+ }
+ }
+ }
+
+ // Check for deadline reached
+ if (json.Contains("\"DeadlineReached\":true"))
+ {
+ result.DeadlineReached = true;
+
+ // Extract deadline reached time
+ int deadlineTimeStart = json.IndexOf("\"DeadlineReachedTime\":\"");
+ if (deadlineTimeStart >= 0)
+ {
+ deadlineTimeStart += "\"DeadlineReachedTime\":\"".Length;
+ int deadlineTimeEnd = json.IndexOf("\"", deadlineTimeStart);
+ if (deadlineTimeEnd > deadlineTimeStart)
+ {
+ string timeStr = json.Substring(deadlineTimeStart, deadlineTimeEnd - deadlineTimeStart);
+ if (DateTime.TryParse(timeStr, out DateTime deadlineReachedTime))
+ {
+ result.DeadlineReachedTime = deadlineReachedTime;
+ }
+ }
+ }
+ }
+ }
+ catch
+ {
+ // Ignore parsing errors
}
-
- // Return the XML as a string
- return doc.GetXml();
}
- ///
- /// Escapes XML special characters
- ///
- /// The text to escape
- /// The escaped text
private string EscapeXml(string text)
{
+ if (string.IsNullOrEmpty(text))
+ {
+ return string.Empty;
+ }
+
return text
.Replace("&", "&")
.Replace("<", "<")
@@ -372,272 +321,5 @@ namespace WindowsNotifications.Services
.Replace("\"", """)
.Replace("'", "'");
}
-
- ///
- /// Handles toast activation events
- ///
- private void OnToastActivated(ToastNotification sender, object args, NotificationOptions options)
- {
- if (!_notificationResults.ContainsKey(options.Id))
- return;
-
- var result = _notificationResults[options.Id];
- result.Activated = true;
- result.InteractionTime = DateTime.Now;
-
- // Handle button clicks
- if (args is ToastActivatedEventArgs activatedArgs)
- {
- string argument = activatedArgs.Arguments;
-
- // Check if this is a custom action
- if (argument == "snooze" || argument == "dismiss")
- {
- // Handle system actions
- result.SystemAction = argument;
- }
- else
- {
- // Handle custom button clicks
- var button = options.Buttons.FirstOrDefault(b => b.Id == argument);
-
- if (button != null)
- {
- result.ClickedButtonId = button.Id;
- result.ClickedButtonText = button.Text;
- result.ClickedButtonArgument = button.Argument;
- }
-
- // Execute custom action if specified
- if (options.OnActivated != null)
- {
- try
- {
- options.OnActivated(result);
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error executing OnActivated action: {ex.Message}");
- }
- }
- }
- }
-
- // Log the activation
- LogNotificationEvent(options, "Activated", result.ClickedButtonText ?? "Direct activation");
-
- // Signal that the notification has been interacted with
- if (_notificationEvents.ContainsKey(options.Id))
- _notificationEvents[options.Id].Set();
- }
-
- ///
- /// Handles toast dismissal events
- ///
- private void OnToastDismissed(ToastNotification sender, ToastDismissedEventArgs args, NotificationOptions options)
- {
- if (!_notificationResults.ContainsKey(options.Id))
- return;
-
- var result = _notificationResults[options.Id];
- result.Dismissed = true;
- result.InteractionTime = DateTime.Now;
- result.DismissalReason = args.Reason.ToString();
-
- string dismissReason = "Unknown";
-
- // Handle different dismissal reasons
- switch (args.Reason)
- {
- case ToastDismissalReason.ApplicationHidden:
- dismissReason = "Application hidden";
- break;
-
- case ToastDismissalReason.UserCanceled:
- dismissReason = "User dismissed";
-
- // Handle deferrals
- if (options.DeferralOptions != null && options.DeferralOptions.Enabled)
- {
- // Get the selected deferral option
- if (args is ToastDismissedEventArgs deferArgs && deferArgs.GetDeferralSelectionId() != null)
- {
- string deferralId = deferArgs.GetDeferralSelectionId();
- var deferOption = options.DeferralOptions.DeferralChoices.FirstOrDefault(d => d.Id == deferralId);
-
- if (deferOption != null)
- {
- result.Deferred = true;
- result.DeferredUntil = DateTime.Now.Add(deferOption.DeferralTime);
- result.DeferralReason = deferOption.Text;
- dismissReason = $"Deferred until {result.DeferredUntil} ({deferOption.Text})";
-
- // Schedule a reminder if needed
- if (options.DeferralOptions.ScheduleReminder)
- {
- ScheduleDeferralReminder(options, deferOption);
- }
- }
- }
- }
- break;
-
- case ToastDismissalReason.TimedOut:
- dismissReason = "Timed out";
-
- // Execute timeout action if specified
- if (options.OnTimeout != null)
- {
- try
- {
- options.OnTimeout(result);
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error executing OnTimeout action: {ex.Message}");
- }
- }
- break;
- }
-
- // Log the dismissal
- LogNotificationEvent(options, "Dismissed", dismissReason);
-
- // Signal that the notification has been interacted with
- if (_notificationEvents.ContainsKey(options.Id))
- _notificationEvents[options.Id].Set();
- }
-
- ///
- /// Handles toast failure events
- ///
- private void OnToastFailed(ToastNotification sender, ToastFailedEventArgs args, NotificationOptions options)
- {
- if (!_notificationResults.ContainsKey(options.Id))
- return;
-
- var result = _notificationResults[options.Id];
- result.ErrorMessage = args.ErrorMessage;
- result.ErrorCode = args.ErrorCode.ToString();
-
- // Log the failure
- LogNotificationEvent(options, "Failed", $"Error: {args.ErrorMessage} (Code: {args.ErrorCode})");
-
- // Execute error action if specified
- if (options.OnError != null)
- {
- try
- {
- options.OnError(result);
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error executing OnError action: {ex.Message}");
- }
- }
-
- // Signal that the notification has failed
- if (_notificationEvents.ContainsKey(options.Id))
- _notificationEvents[options.Id].Set();
- }
-
- ///
- /// Logs a notification event
- ///
- /// The notification options
- /// The type of event
- /// Additional details about the event
- private void LogNotificationEvent(NotificationOptions options, string eventType, string details)
- {
- if (!options.EnableLogging)
- return;
-
- try
- {
- // Get the current user session
- var sessionManager = new UserSessionManager();
- var currentUser = sessionManager.GetCurrentInteractiveUser();
- string userInfo = currentUser != null ? $"{currentUser.DomainName}\\{currentUser.UserName} (Session: {currentUser.SessionId})" : "Unknown user";
-
- // Create the log entry
- string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{eventType}] ID: {options.Id}, Title: {options.Title}, User: {userInfo}, Details: {details}";
-
- // Log to the specified action
- if (options.LogAction != null)
- {
- options.LogAction(logEntry);
- }
- else
- {
- // Default logging to debug output
- System.Diagnostics.Debug.WriteLine(logEntry);
- }
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error logging notification event: {ex.Message}");
- }
- }
-
- ///
- /// Schedules a reminder for a deferred notification
- ///
- /// The notification options
- /// The selected deferral option
- private void ScheduleDeferralReminder(NotificationOptions options, DeferralOption deferOption)
- {
- try
- {
- // Calculate the reminder time
- DateTime reminderTime = DateTime.Now.Add(deferOption.DeferralTime);
-
- // Create a task scheduler task
- using (TaskService ts = new TaskService())
- {
- // Create a new task definition
- TaskDefinition td = ts.NewTask();
- td.RegistrationInfo.Description = $"Reminder for notification: {options.Title}";
-
- // Create a trigger that will fire at the reminder time
- td.Triggers.Add(new TimeTrigger(reminderTime));
-
- // Create an action to show the reminder notification
- string actionPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
- string actionArgs = $"-ExecutionPolicy Bypass -Command \"& {{ $bytes = [System.Convert]::FromBase64String('{options.ReminderData}'); $assembly = [System.Reflection.Assembly]::Load($bytes); $notificationManager = New-Object WindowsNotifications.NotificationManager; $options = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::Deserialize([System.IO.MemoryStream]::new($bytes)); $notificationManager.ShowNotification($options); }}\"";
-
- td.Actions.Add(new ExecAction(actionPath, actionArgs, null));
-
- // Register the task
- ts.RootFolder.RegisterTaskDefinition($"WindowsNotification_Reminder_{options.Id}", td);
- }
-
- // Log the scheduled reminder
- LogNotificationEvent(options, "ReminderScheduled", $"Reminder scheduled for {reminderTime}");
- }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"Error scheduling deferral reminder: {ex.Message}");
- }
- }
-
- ///
- /// Registers the COM server for notifications
- ///
- private void RegisterComServer()
- {
- try
- {
- // Register the AppID for the current process
- string appUserModelId = APP_ID;
- SetCurrentProcessExplicitAppUserModelID(appUserModelId);
- }
- catch (Exception ex)
- {
- throw new Exception("Failed to register COM server for notifications", ex);
- }
- }
-
- [DllImport("shell32.dll", SetLastError = true)]
- private static extern int SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
}
}
diff --git a/WindowsNotifications/Services/UserImpersonation.cs b/WindowsNotifications/Services/UserImpersonation.cs
index 7da3e05..bb84c8c 100644
--- a/WindowsNotifications/Services/UserImpersonation.cs
+++ b/WindowsNotifications/Services/UserImpersonation.cs
@@ -1,280 +1,275 @@
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
-using System.ComponentModel;
-using System.Security.Permissions;
namespace WindowsNotifications.Services
{
///
- /// Provides functionality for impersonating a user from SYSTEM context
+ /// Utility for impersonating users.
///
- internal class UserImpersonation : IDisposable
+ internal static class UserImpersonation
{
+ ///
+ /// Runs an action as the specified user.
+ ///
+ /// The return type of the action.
+ /// The name of the user to impersonate.
+ /// The action to run.
+ /// The result of the action.
+ public static T RunAsUser(string userName, Func action)
+ {
+ if (string.IsNullOrEmpty(userName))
+ {
+ return action();
+ }
+
+ IntPtr userToken = IntPtr.Zero;
+ IntPtr duplicateToken = IntPtr.Zero;
+ WindowsImpersonationContext impersonationContext = null;
+
+ try
+ {
+ // Get the token for the user
+ if (!GetSessionUserToken(ref userToken, userName))
+ {
+ return action();
+ }
+
+ // Duplicate the token
+ if (!DuplicateToken(userToken, 2, ref duplicateToken))
+ {
+ return action();
+ }
+
+ // Create a WindowsIdentity from the token
+ using (WindowsIdentity identity = new WindowsIdentity(duplicateToken))
+ {
+ // Impersonate the user
+ impersonationContext = identity.Impersonate();
+
+ // Run the action
+ return action();
+ }
+ }
+ finally
+ {
+ // Clean up
+ if (impersonationContext != null)
+ {
+ impersonationContext.Undo();
+ }
+
+ if (userToken != IntPtr.Zero)
+ {
+ CloseHandle(userToken);
+ }
+
+ if (duplicateToken != IntPtr.Zero)
+ {
+ CloseHandle(duplicateToken);
+ }
+ }
+ }
+
+ private static bool GetSessionUserToken(ref IntPtr token, string userName)
+ {
+ IntPtr wtsToken = IntPtr.Zero;
+ int sessionId = -1;
+
+ try
+ {
+ // Find the session ID for the user
+ IntPtr serverHandle = IntPtr.Zero;
+ IntPtr sessionInfo = IntPtr.Zero;
+ int sessionCount = 0;
+ int retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref sessionInfo, ref sessionCount);
+
+ if (retVal != 0)
+ {
+ int dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
+ IntPtr currentSession = sessionInfo;
+
+ for (int i = 0; i < sessionCount; i++)
+ {
+ WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure(currentSession, typeof(WTS_SESSION_INFO));
+ currentSession = IntPtr.Add(currentSession, dataSize);
+
+ if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ int bytesReturned = 0;
+
+ if (WTSQuerySessionInformation(IntPtr.Zero, si.SessionID, WTS_INFO_CLASS.WTSUserName, out buffer, out bytesReturned) && bytesReturned > 1)
+ {
+ string sessionUserName = Marshal.PtrToStringAnsi(buffer);
+ WTSFreeMemory(buffer);
+
+ if (string.Equals(sessionUserName, userName, StringComparison.OrdinalIgnoreCase))
+ {
+ sessionId = si.SessionID;
+ break;
+ }
+ }
+ }
+ }
+
+ WTSFreeMemory(sessionInfo);
+ }
+
+ if (sessionId == -1)
+ {
+ return false;
+ }
+
+ // Get the user token for the session
+ if (!WTSQueryUserToken(sessionId, ref wtsToken))
+ {
+ return false;
+ }
+
+ // Duplicate the token
+ if (!DuplicateTokenEx(wtsToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, ref token))
+ {
+ return false;
+ }
+
+ return true;
+ }
+ finally
+ {
+ if (wtsToken != IntPtr.Zero)
+ {
+ CloseHandle(wtsToken);
+ }
+ }
+ }
+
#region Win32 API
+ private const int TOKEN_ASSIGN_PRIMARY = 0x0001;
+ private const int TOKEN_DUPLICATE = 0x0002;
+ private const int TOKEN_IMPERSONATE = 0x0004;
+ private const int TOKEN_QUERY = 0x0008;
+ private const int TOKEN_QUERY_SOURCE = 0x0010;
+ private const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
+ private const int TOKEN_ADJUST_GROUPS = 0x0040;
+ private const int TOKEN_ADJUST_DEFAULT = 0x0080;
+ private const int TOKEN_ADJUST_SESSIONID = 0x0100;
+ private const int TOKEN_ALL_ACCESS = TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID;
+
+ private enum WTS_INFO_CLASS
+ {
+ WTSInitialProgram,
+ WTSApplicationName,
+ WTSWorkingDirectory,
+ WTSOEMId,
+ WTSSessionId,
+ WTSUserName,
+ WTSWinStationName,
+ WTSDomainName,
+ WTSConnectState,
+ WTSClientBuildNumber,
+ WTSClientName,
+ WTSClientDirectory,
+ WTSClientProductId,
+ WTSClientHardwareId,
+ WTSClientAddress,
+ WTSClientDisplay,
+ WTSClientProtocolType,
+ WTSIdleTime,
+ WTSLogonTime,
+ WTSIncomingBytes,
+ WTSOutgoingBytes,
+ WTSIncomingFrames,
+ WTSOutgoingFrames,
+ WTSClientInfo,
+ WTSSessionInfo,
+ WTSSessionInfoEx,
+ WTSConfigInfo,
+ WTSValidationInfo,
+ WTSSessionAddressV4,
+ WTSIsRemoteSession
+ }
+
+ private enum WTS_CONNECTSTATE_CLASS
+ {
+ WTSActive,
+ WTSConnected,
+ WTSConnectQuery,
+ WTSShadow,
+ WTSDisconnected,
+ WTSIdle,
+ WTSListen,
+ WTSReset,
+ WTSDown,
+ WTSInit
+ }
+
+ private enum SECURITY_IMPERSONATION_LEVEL
+ {
+ SecurityAnonymous,
+ SecurityIdentification,
+ SecurityImpersonation,
+ SecurityDelegation
+ }
+
+ private enum TOKEN_TYPE
+ {
+ TokenPrimary = 1,
+ TokenImpersonation
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct WTS_SESSION_INFO
+ {
+ public int SessionID;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string pWinStationName;
+ public WTS_CONNECTSTATE_CLASS State;
+ }
+
+ [DllImport("wtsapi32.dll", SetLastError = true)]
+ private static extern int WTSEnumerateSessions(
+ IntPtr hServer,
+ int Reserved,
+ int Version,
+ ref IntPtr ppSessionInfo,
+ ref int pCount);
+
+ [DllImport("wtsapi32.dll")]
+ private static extern void WTSFreeMemory(IntPtr pMemory);
+
+ [DllImport("wtsapi32.dll", SetLastError = true)]
+ private static extern bool WTSQuerySessionInformation(
+ IntPtr hServer,
+ int sessionId,
+ WTS_INFO_CLASS wtsInfoClass,
+ out IntPtr ppBuffer,
+ out int pBytesReturned);
+
+ [DllImport("wtsapi32.dll", SetLastError = true)]
+ private static extern bool WTSQueryUserToken(
+ int sessionId,
+ ref IntPtr phToken);
+
[DllImport("advapi32.dll", SetLastError = true)]
- private static extern bool LogonUser(
- string lpszUsername,
- string lpszDomain,
- string lpszPassword,
- int dwLogonType,
- int dwLogonProvider,
- out IntPtr phToken);
+ private static extern bool DuplicateTokenEx(
+ IntPtr hExistingToken,
+ int dwDesiredAccess,
+ IntPtr lpTokenAttributes,
+ SECURITY_IMPERSONATION_LEVEL impersonationLevel,
+ TOKEN_TYPE tokenType,
+ ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool DuplicateToken(
IntPtr ExistingTokenHandle,
- int ImpersonationLevel,
- out IntPtr DuplicateTokenHandle);
-
- [DllImport("advapi32.dll", SetLastError = true)]
- private static extern bool ImpersonateLoggedOnUser(IntPtr hToken);
-
- [DllImport("advapi32.dll", SetLastError = true)]
- private static extern bool RevertToSelf();
+ int SECURITY_IMPERSONATION_LEVEL,
+ ref IntPtr DuplicateTokenHandle);
[DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool CloseHandle(IntPtr hObject);
-
- [DllImport("wtsapi32.dll", SetLastError = true)]
- private static extern bool WTSQueryUserToken(uint sessionId, out IntPtr phToken);
-
- [DllImport("userenv.dll", SetLastError = true)]
- private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
-
- [DllImport("userenv.dll", SetLastError = true)]
- private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
-
- private const int LOGON32_LOGON_INTERACTIVE = 2;
- private const int LOGON32_PROVIDER_DEFAULT = 0;
- private const int LOGON32_PROVIDER_WINNT50 = 3;
- private const int SECURITY_IMPERSONATION = 2;
+ private static extern bool CloseHandle(IntPtr hHandle);
#endregion
-
- private IntPtr _userToken = IntPtr.Zero;
- private IntPtr _impersonationToken = IntPtr.Zero;
- private IntPtr _environmentBlock = IntPtr.Zero;
- private bool _disposed = false;
- private bool _isImpersonating = false;
-
- ///
- /// Gets whether the current process is running as SYSTEM
- ///
- public static bool IsRunningAsSystem
- {
- get
- {
- using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
- {
- return identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid);
- }
- }
- }
-
- ///
- /// Impersonates a user by session ID
- ///
- /// The session ID to impersonate
- /// True if impersonation was successful, false otherwise
- public bool ImpersonateBySessionId(int sessionId)
- {
- if (_isImpersonating)
- return false;
-
- try
- {
- if (!WTSQueryUserToken((uint)sessionId, out _userToken))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to get user token for session");
- }
-
- if (!DuplicateToken(_userToken, SECURITY_IMPERSONATION, out _impersonationToken))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate token");
- }
-
- if (!CreateEnvironmentBlock(out _environmentBlock, _impersonationToken, false))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to create environment block");
- }
-
- if (!ImpersonateLoggedOnUser(_impersonationToken))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to impersonate user");
- }
-
- _isImpersonating = true;
- return true;
- }
- catch (Exception ex)
- {
- CleanupTokens();
- throw new Exception("Impersonation failed", ex);
- }
- }
-
- ///
- /// Impersonates a user by username and password
- ///
- /// The username to impersonate
- /// The domain of the user
- /// The password of the user
- /// True if impersonation was successful, false otherwise
- public bool ImpersonateUser(string username, string domain, string password)
- {
- if (_isImpersonating)
- return false;
-
- try
- {
- if (!LogonUser(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out _userToken))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to log on as user");
- }
-
- if (!DuplicateToken(_userToken, SECURITY_IMPERSONATION, out _impersonationToken))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to duplicate token");
- }
-
- if (!CreateEnvironmentBlock(out _environmentBlock, _impersonationToken, false))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to create environment block");
- }
-
- if (!ImpersonateLoggedOnUser(_impersonationToken))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to impersonate user");
- }
-
- _isImpersonating = true;
- return true;
- }
- catch (Exception ex)
- {
- CleanupTokens();
- throw new Exception("Impersonation failed", ex);
- }
- }
-
- ///
- /// Stops impersonating the user and reverts to the original security context
- ///
- public void StopImpersonation()
- {
- if (_isImpersonating)
- {
- RevertToSelf();
- _isImpersonating = false;
- }
-
- CleanupTokens();
- }
-
- ///
- /// Executes an action while impersonating a user
- ///
- /// The session ID to impersonate
- /// The action to execute
- /// True if the action was executed successfully, false otherwise
- public bool ExecuteAsUser(int sessionId, Action action)
- {
- if (!ImpersonateBySessionId(sessionId))
- return false;
-
- try
- {
- action();
- return true;
- }
- finally
- {
- StopImpersonation();
- }
- }
-
- ///
- /// Executes a function while impersonating a user and returns the result
- ///
- /// The return type of the function
- /// The session ID to impersonate
- /// The function to execute
- /// The result of the function
- public T ExecuteAsUser(int sessionId, Func func)
- {
- if (!ImpersonateBySessionId(sessionId))
- throw new Exception("Failed to impersonate user");
-
- try
- {
- return func();
- }
- finally
- {
- StopImpersonation();
- }
- }
-
- ///
- /// Cleans up any open tokens
- ///
- private void CleanupTokens()
- {
- if (_environmentBlock != IntPtr.Zero)
- {
- DestroyEnvironmentBlock(_environmentBlock);
- _environmentBlock = IntPtr.Zero;
- }
-
- if (_impersonationToken != IntPtr.Zero)
- {
- CloseHandle(_impersonationToken);
- _impersonationToken = IntPtr.Zero;
- }
-
- if (_userToken != IntPtr.Zero)
- {
- CloseHandle(_userToken);
- _userToken = IntPtr.Zero;
- }
- }
-
- ///
- /// Disposes of resources
- ///
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- ///
- /// Disposes of resources
- ///
- /// Whether this is being called from Dispose() or the finalizer
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposed)
- {
- if (disposing)
- {
- // Dispose managed resources
- }
-
- // Dispose unmanaged resources
- StopImpersonation();
- _disposed = true;
- }
- }
-
- ///
- /// Finalizer
- ///
- ~UserImpersonation()
- {
- Dispose(false);
- }
}
}
diff --git a/WindowsNotifications/Services/UserSessionManager.cs b/WindowsNotifications/Services/UserSessionManager.cs
index 97aeab8..fe40a4c 100644
--- a/WindowsNotifications/Services/UserSessionManager.cs
+++ b/WindowsNotifications/Services/UserSessionManager.cs
@@ -1,85 +1,166 @@
using System;
using System.Collections.Generic;
-using System.Runtime.InteropServices;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Security.Principal;
-using System.Diagnostics;
namespace WindowsNotifications.Services
{
///
- /// Manages user sessions and detects interactive sessions
+ /// Service for managing user sessions and impersonation.
///
internal class UserSessionManager
{
- #region Win32 API
-
- [DllImport("wtsapi32.dll")]
- private static extern bool WTSEnumerateSessions(
- IntPtr hServer,
- int Reserved,
- int Version,
- ref IntPtr ppSessionInfo,
- ref int pCount);
-
- [DllImport("wtsapi32.dll")]
- private static extern void WTSFreeMemory(IntPtr pMemory);
-
- [DllImport("wtsapi32.dll")]
- private static extern bool WTSQuerySessionInformation(
- IntPtr hServer,
- int sessionId,
- WTS_INFO_CLASS wtsInfoClass,
- out IntPtr ppBuffer,
- out int pBytesReturned);
-
- [DllImport("kernel32.dll")]
- private static extern uint WTSGetActiveConsoleSessionId();
-
- [StructLayout(LayoutKind.Sequential)]
- private struct WTS_SESSION_INFO
+ ///
+ /// Checks if the current process is running as SYSTEM.
+ ///
+ /// True if running as SYSTEM, false otherwise.
+ public bool IsRunningAsSystem()
{
- public int SessionId;
- [MarshalAs(UnmanagedType.LPStr)]
- public string pWinStationName;
- public WTS_CONNECTSTATE_CLASS State;
+ using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
+ {
+ return identity.IsSystem;
+ }
}
+ ///
+ /// Checks if there's an interactive user session.
+ ///
+ /// True if there's an interactive user session, false otherwise.
+ public bool HasInteractiveUserSession()
+ {
+ return GetInteractiveUserSessions().Count > 0;
+ }
+
+ ///
+ /// Gets all interactive user sessions.
+ ///
+ /// A list of interactive user sessions.
+ public List GetInteractiveUserSessions()
+ {
+ List sessions = new List();
+
+ try
+ {
+ IntPtr serverHandle = IntPtr.Zero;
+ IntPtr sessionInfo = IntPtr.Zero;
+ int sessionCount = 0;
+ int retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref sessionInfo, ref sessionCount);
+
+ if (retVal != 0)
+ {
+ int dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
+ IntPtr currentSession = sessionInfo;
+
+ for (int i = 0; i < sessionCount; i++)
+ {
+ WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure(currentSession, typeof(WTS_SESSION_INFO));
+ currentSession = IntPtr.Add(currentSession, dataSize);
+
+ if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
+ {
+ string userName = GetUserNameFromSessionId(si.SessionID);
+ if (!string.IsNullOrEmpty(userName) && !userName.Equals("SYSTEM", StringComparison.OrdinalIgnoreCase))
+ {
+ sessions.Add(userName);
+ }
+ }
+ }
+
+ WTSFreeMemory(sessionInfo);
+ }
+ }
+ catch
+ {
+ // Ignore errors
+ }
+
+ return sessions;
+ }
+
+ ///
+ /// Runs an action as the interactive user.
+ ///
+ /// The return type of the action.
+ /// The action to run.
+ /// The result of the action.
+ public T RunAsInteractiveUser(Func action)
+ {
+ if (!IsRunningAsSystem())
+ {
+ return action();
+ }
+
+ List sessions = GetInteractiveUserSessions();
+ if (sessions.Count == 0)
+ {
+ return action();
+ }
+
+ return UserImpersonation.RunAsUser(sessions[0], action);
+ }
+
+ private string GetUserNameFromSessionId(int sessionId)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ int bytesReturned = 0;
+ string userName = string.Empty;
+
+ try
+ {
+ if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WTS_INFO_CLASS.WTSUserName, out buffer, out bytesReturned) && bytesReturned > 1)
+ {
+ userName = Marshal.PtrToStringAnsi(buffer);
+ }
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ WTSFreeMemory(buffer);
+ }
+ }
+
+ return userName;
+ }
+
+ #region Win32 API
+
private enum WTS_INFO_CLASS
{
- WTSInitialProgram = 0,
- WTSApplicationName = 1,
- WTSWorkingDirectory = 2,
- WTSOEMId = 3,
- WTSSessionId = 4,
- WTSUserName = 5,
- WTSWinStationName = 6,
- WTSDomainName = 7,
- WTSConnectState = 8,
- WTSClientBuildNumber = 9,
- WTSClientName = 10,
- WTSClientDirectory = 11,
- WTSClientProductId = 12,
- WTSClientHardwareId = 13,
- WTSClientAddress = 14,
- WTSClientDisplay = 15,
- WTSClientProtocolType = 16,
- WTSIdleTime = 17,
- WTSLogonTime = 18,
- WTSIncomingBytes = 19,
- WTSOutgoingBytes = 20,
- WTSIncomingFrames = 21,
- WTSOutgoingFrames = 22,
- WTSClientInfo = 23,
- WTSSessionInfo = 24,
- WTSSessionInfoEx = 25,
- WTSConfigInfo = 26,
- WTSValidationInfo = 27,
- WTSSessionAddressV4 = 28,
- WTSIsRemoteSession = 29
+ WTSInitialProgram,
+ WTSApplicationName,
+ WTSWorkingDirectory,
+ WTSOEMId,
+ WTSSessionId,
+ WTSUserName,
+ WTSWinStationName,
+ WTSDomainName,
+ WTSConnectState,
+ WTSClientBuildNumber,
+ WTSClientName,
+ WTSClientDirectory,
+ WTSClientProductId,
+ WTSClientHardwareId,
+ WTSClientAddress,
+ WTSClientDisplay,
+ WTSClientProtocolType,
+ WTSIdleTime,
+ WTSLogonTime,
+ WTSIncomingBytes,
+ WTSOutgoingBytes,
+ WTSIncomingFrames,
+ WTSOutgoingFrames,
+ WTSClientInfo,
+ WTSSessionInfo,
+ WTSSessionInfoEx,
+ WTSConfigInfo,
+ WTSValidationInfo,
+ WTSSessionAddressV4,
+ WTSIsRemoteSession
}
- public enum WTS_CONNECTSTATE_CLASS
+ private enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
@@ -93,260 +174,34 @@ namespace WindowsNotifications.Services
WTSInit
}
- private const int WTS_CURRENT_SERVER_HANDLE = 0;
+ [StructLayout(LayoutKind.Sequential)]
+ private struct WTS_SESSION_INFO
+ {
+ public int SessionID;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string pWinStationName;
+ public WTS_CONNECTSTATE_CLASS State;
+ }
+
+ [DllImport("wtsapi32.dll", SetLastError = true)]
+ private static extern int WTSEnumerateSessions(
+ IntPtr hServer,
+ int Reserved,
+ int Version,
+ ref IntPtr ppSessionInfo,
+ ref int pCount);
+
+ [DllImport("wtsapi32.dll")]
+ private static extern void WTSFreeMemory(IntPtr pMemory);
+
+ [DllImport("wtsapi32.dll", SetLastError = true)]
+ private static extern bool WTSQuerySessionInformation(
+ IntPtr hServer,
+ int sessionId,
+ WTS_INFO_CLASS wtsInfoClass,
+ out IntPtr ppBuffer,
+ out int pBytesReturned);
#endregion
-
- ///
- /// Represents a user session
- ///
- public class UserSession
- {
- public int SessionId { get; set; }
- public string UserName { get; set; }
- public string DomainName { get; set; }
- public string StationName { get; set; }
- public WTS_CONNECTSTATE_CLASS State { get; set; }
- public bool IsActiveConsoleSession { get; set; }
- public bool IsRemoteSession { get; set; }
-
- public override string ToString()
- {
- return $"{DomainName}\\{UserName} (Session: {SessionId}, State: {State}, Console: {IsActiveConsoleSession}, Remote: {IsRemoteSession})";
- }
- }
-
- ///
- /// Gets all user sessions
- ///
- /// A list of user sessions
- public List GetUserSessions()
- {
- var sessions = new List();
- IntPtr ppSessionInfo = IntPtr.Zero;
- int count = 0;
- int activeConsoleSessionId = (int)WTSGetActiveConsoleSessionId();
-
- try
- {
- if (WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref ppSessionInfo, ref count))
- {
- IntPtr current = ppSessionInfo;
-
- for (int i = 0; i < count; i++)
- {
- WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO));
- current = IntPtr.Add(current, Marshal.SizeOf(typeof(WTS_SESSION_INFO)));
-
- // Skip non-active sessions
- if (si.State != WTS_CONNECTSTATE_CLASS.WTSActive)
- continue;
-
- string userName = GetSessionInfo(si.SessionId, WTS_INFO_CLASS.WTSUserName);
- string domainName = GetSessionInfo(si.SessionId, WTS_INFO_CLASS.WTSDomainName);
- bool isRemoteSession = GetIsRemoteSession(si.SessionId);
-
- // Skip sessions without a user name (system sessions)
- if (string.IsNullOrEmpty(userName))
- continue;
-
- sessions.Add(new UserSession
- {
- SessionId = si.SessionId,
- UserName = userName,
- DomainName = domainName,
- StationName = si.pWinStationName,
- State = si.State,
- IsActiveConsoleSession = si.SessionId == activeConsoleSessionId,
- IsRemoteSession = isRemoteSession
- });
- }
- }
- }
- finally
- {
- if (ppSessionInfo != IntPtr.Zero)
- WTSFreeMemory(ppSessionInfo);
- }
-
- return sessions;
- }
-
- ///
- /// Gets all interactive user sessions (console or RDP)
- ///
- /// A list of interactive user sessions
- public List GetInteractiveUserSessions()
- {
- return GetUserSessions().Where(s => s.State == WTS_CONNECTSTATE_CLASS.WTSActive).ToList();
- }
-
- ///
- /// Gets the active console session, if any
- ///
- /// The active console session, or null if none exists
- public UserSession GetActiveConsoleSession()
- {
- return GetUserSessions().FirstOrDefault(s => s.IsActiveConsoleSession);
- }
-
- ///
- /// Gets the session information for the specified session ID and info class
- ///
- /// The session ID
- /// The information class to retrieve
- /// The session information
- private string GetSessionInfo(int sessionId, WTS_INFO_CLASS infoClass)
- {
- IntPtr ppBuffer = IntPtr.Zero;
- int bytesReturned = 0;
- string result = string.Empty;
-
- try
- {
- if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, infoClass, out ppBuffer, out bytesReturned) && ppBuffer != IntPtr.Zero)
- {
- result = Marshal.PtrToStringAnsi(ppBuffer);
- }
- }
- finally
- {
- if (ppBuffer != IntPtr.Zero)
- WTSFreeMemory(ppBuffer);
- }
-
- return result;
- }
-
- ///
- /// Determines if the specified session is a remote session
- ///
- /// The session ID
- /// True if the session is remote, false otherwise
- private bool GetIsRemoteSession(int sessionId)
- {
- IntPtr ppBuffer = IntPtr.Zero;
- int bytesReturned = 0;
- bool result = false;
-
- try
- {
- if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WTS_INFO_CLASS.WTSIsRemoteSession, out ppBuffer, out bytesReturned) && ppBuffer != IntPtr.Zero)
- {
- result = Marshal.ReadInt32(ppBuffer) != 0;
- }
- }
- finally
- {
- if (ppBuffer != IntPtr.Zero)
- WTSFreeMemory(ppBuffer);
- }
-
- return result;
- }
-
- ///
- /// Gets the current interactive user information
- ///
- /// A UserSession object representing the current interactive user, or null if none exists
- public UserSession GetCurrentInteractiveUser()
- {
- try
- {
- // First try to get the active console session
- var consoleSession = GetActiveConsoleSession();
- if (consoleSession != null && !string.IsNullOrEmpty(consoleSession.UserName))
- {
- return consoleSession;
- }
-
- // If no active console session, try to find any active session
- var activeSessions = GetInteractiveUserSessions();
- if (activeSessions.Count > 0)
- {
- return activeSessions[0];
- }
-
- // If no active sessions, try to get the current user
- using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
- {
- if (identity != null && !identity.IsSystem && !identity.IsAnonymous)
- {
- string[] parts = identity.Name.Split('\\');
- string domain = parts.Length > 1 ? parts[0] : "";
- string username = parts.Length > 1 ? parts[1] : parts[0];
-
- return new UserSession
- {
- SessionId = -1, // Unknown session ID
- UserName = username,
- DomainName = domain,
- StationName = "Unknown",
- State = WTS_CONNECTSTATE_CLASS.WTSActive,
- IsActiveConsoleSession = false,
- IsRemoteSession = false
- };
- }
- }
-
- // If all else fails, try to get the user from the process owner
- Process currentProcess = Process.GetCurrentProcess();
- string processOwner = GetProcessOwner(currentProcess.Id);
- if (!string.IsNullOrEmpty(processOwner))
- {
- string[] parts = processOwner.Split('\\');
- string domain = parts.Length > 1 ? parts[0] : "";
- string username = parts.Length > 1 ? parts[1] : parts[0];
-
- return new UserSession
- {
- SessionId = -1, // Unknown session ID
- UserName = username,
- DomainName = domain,
- StationName = "Unknown",
- State = WTS_CONNECTSTATE_CLASS.WTSActive,
- IsActiveConsoleSession = false,
- IsRemoteSession = false
- };
- }
-
- return null;
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error getting current interactive user: {ex.Message}");
- return null;
- }
- }
-
- ///
- /// Gets the owner of a process
- ///
- /// The process ID
- /// The process owner in the format DOMAIN\Username
- private string GetProcessOwner(int processId)
- {
- try
- {
- Process process = Process.GetProcessById(processId);
- using (var searcher = new System.Management.ManagementObjectSearcher($"SELECT * FROM Win32_Process WHERE ProcessId = {processId}"))
- {
- foreach (var obj in searcher.Get())
- {
- string[] args = new string[2];
- obj.InvokeMethod("GetOwner", args);
- if (!string.IsNullOrEmpty(args[0]))
- {
- return $"{args[1]}\\{args[0]}"; // DOMAIN\Username
- }
- }
- }
- return string.Empty;
- }
- catch
- {
- return string.Empty;
- }
- }
}
}
diff --git a/WindowsNotifications/SimpleNotificationManager.cs b/WindowsNotifications/SimpleNotificationManager.cs
deleted file mode 100644
index 3e5b1f3..0000000
--- a/WindowsNotifications/SimpleNotificationManager.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using WindowsNotifications.Models;
-
-namespace WindowsNotifications
-{
- ///
- /// Simple implementation of the Windows Notifications library
- ///
- public class SimpleNotificationManager
- {
- private readonly string _databasePath;
- private readonly Dictionary _reminderTimers = new Dictionary();
-
- ///
- /// Gets or sets the path to the database file
- ///
- public string DatabasePath { get; private set; }
-
- ///
- /// Creates a new SimpleNotificationManager with the default database path
- ///
- public SimpleNotificationManager() : this(null)
- {
- }
-
- ///
- /// Creates a new SimpleNotificationManager with the specified database path
- ///
- /// The path to the database file, or null to use the default
- public SimpleNotificationManager(string databasePath)
- {
- DatabasePath = databasePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "WindowsNotifications", "notifications.db");
- }
-
- ///
- /// Shows a notification with the specified options
- ///
- /// The notification options
- /// The result of the notification
- public NotificationResult ShowNotification(NotificationOptions options)
- {
- // Validate options
- if (options == null)
- throw new ArgumentNullException(nameof(options));
-
- if (string.IsNullOrEmpty(options.Title) && string.IsNullOrEmpty(options.Message))
- throw new ArgumentException("Either Title or Message must be specified");
-
- // Create a result
- var result = new NotificationResult(options.Id)
- {
- Displayed = true,
- CreatedTime = DateTime.Now
- };
-
- // Log the notification
- if (options.EnableLogging)
- {
- string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Notification displayed: ID={options.Id}, Title={options.Title}";
- Debug.WriteLine(logEntry);
- options.LogAction?.Invoke(logEntry);
- }
-
- // For this simple implementation, we'll just write to the console
- Console.WriteLine($"Notification: {options.Title}");
- Console.WriteLine($"Message: {options.Message}");
-
- if (options.Buttons.Count > 0)
- {
- Console.WriteLine("Buttons:");
- foreach (var button in options.Buttons)
- {
- Console.WriteLine($"- {button.Text} (ID: {button.Id})");
- }
- }
-
- // Simulate user interaction
- if (!options.Async)
- {
- // Simulate a delay
- Thread.Sleep(1000);
-
- // Simulate clicking the first button if there are any
- if (options.Buttons.Count > 0)
- {
- var button = options.Buttons[0];
- result.Activated = true;
- result.ClickedButtonId = button.Id;
- result.ClickedButtonText = button.Text;
- result.ClickedButtonArgument = button.Argument;
- result.InteractionTime = DateTime.Now;
- }
- else
- {
- // Simulate dismissal
- result.Dismissed = true;
- result.InteractionTime = DateTime.Now;
- }
- }
-
- return result;
- }
-
- ///
- /// Shows a simple notification with the specified title and message
- ///
- /// The title of the notification
- /// The message body of the notification
- /// The result of the notification
- public NotificationResult ShowSimpleNotification(string title, string message)
- {
- var options = new NotificationOptions
- {
- Title = title,
- Message = message
- };
-
- return ShowNotification(options);
- }
-
- ///
- /// Shows a notification with buttons
- ///
- /// The title of the notification
- /// The message body of the notification
- /// The buttons to display
- /// The result of the notification
- public NotificationResult ShowNotificationWithButtons(string title, string message, params string[] buttons)
- {
- var options = new NotificationOptions
- {
- Title = title,
- Message = message,
- Buttons = buttons.Select(b => new NotificationButton(b)).ToList()
- };
-
- return ShowNotification(options);
- }
-
- ///
- /// Gets the result of a notification
- ///
- /// The ID of the notification
- /// The notification result, or null if not found
- public NotificationResult GetNotificationResult(string notificationId)
- {
- // In this simple implementation, we always return null
- return null;
- }
-
- ///
- /// Waits for a notification to complete
- ///
- /// The ID of the notification
- /// The timeout in milliseconds, or -1 to wait indefinitely
- /// The notification result, or null if timed out or not found
- public NotificationResult WaitForNotification(string notificationId, int timeout = -1)
- {
- // In this simple implementation, we always return null
- return null;
- }
-
- ///
- /// Gets all notification results
- ///
- /// A list of notification results
- public List GetAllNotificationResults()
- {
- // In this simple implementation, we always return an empty list
- return new List();
- }
-
- ///
- /// Deletes a notification result
- ///
- /// The ID of the notification
- /// True if the deletion was successful, false otherwise
- public bool DeleteNotificationResult(string notificationId)
- {
- // In this simple implementation, we always return true
- return true;
- }
-
- ///
- /// Deletes all notification results
- ///
- /// True if the deletion was successful, false otherwise
- public bool DeleteAllNotificationResults()
- {
- // In this simple implementation, we always return true
- return true;
- }
-
- ///
- /// Gets the path to the database file
- ///
- /// The database file path
- public string GetDatabaseFilePath()
- {
- return DatabasePath;
- }
-
- ///
- /// Checks if the current process is running as SYSTEM
- ///
- /// True if running as SYSTEM, false otherwise
- public bool IsRunningAsSystem()
- {
- // In this simple implementation, we always return false
- return false;
- }
-
- ///
- /// Gets all interactive user sessions
- ///
- /// A list of interactive user sessions
- public List GetInteractiveUserSessions()
- {
- // In this simple implementation, we always return an empty list
- return new List();
- }
- }
-}
diff --git a/WindowsNotifications/SimpleWindowsNotifications.csproj b/WindowsNotifications/SimpleWindowsNotifications.csproj
deleted file mode 100644
index e061938..0000000
--- a/WindowsNotifications/SimpleWindowsNotifications.csproj
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}
- Library
- Properties
- WindowsNotifications
- WindowsNotifications
- v4.7.2
- 512
- true
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/WindowsNotifications/Utils/AssemblyLoader.cs b/WindowsNotifications/Utils/AssemblyLoader.cs
index d2b1d80..37c134d 100644
--- a/WindowsNotifications/Utils/AssemblyLoader.cs
+++ b/WindowsNotifications/Utils/AssemblyLoader.cs
@@ -5,88 +5,51 @@ using System.Reflection;
namespace WindowsNotifications.Utils
{
///
- /// Provides utilities for loading assemblies in PowerShell
+ /// Utility for loading assemblies.
///
- public static class AssemblyLoader
+ internal static class AssemblyLoader
{
///
- /// Gets the current assembly as a byte array
+ /// Gets the bytes of an embedded resource.
///
- /// The assembly as a byte array
- public static byte[] GetAssemblyBytes()
+ /// The name of the resource.
+ /// The bytes of the resource.
+ public static byte[] GetEmbeddedResourceBytes(string resourceName)
{
- try
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
- string assemblyPath = Assembly.GetExecutingAssembly().Location;
- return File.ReadAllBytes(assemblyPath);
- }
- catch (Exception ex)
- {
- throw new Exception("Failed to get assembly bytes", ex);
+ if (stream == null)
+ {
+ throw new Exception($"Resource {resourceName} not found.");
+ }
+
+ byte[] buffer = new byte[stream.Length];
+ stream.Read(buffer, 0, buffer.Length);
+ return buffer;
}
}
///
- /// Gets the current assembly as a Base64 encoded string
+ /// Gets the text of an embedded resource.
///
- /// The assembly as a Base64 encoded string
- public static string GetAssemblyBase64()
+ /// The name of the resource.
+ /// The text of the resource.
+ public static string GetEmbeddedResourceText(string resourceName)
{
- try
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
- byte[] bytes = GetAssemblyBytes();
- return Convert.ToBase64String(bytes);
+ if (stream == null)
+ {
+ throw new Exception($"Resource {resourceName} not found.");
+ }
+
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ return reader.ReadToEnd();
+ }
}
- catch (Exception ex)
- {
- throw new Exception("Failed to get assembly as Base64", ex);
- }
- }
-
- ///
- /// Gets a PowerShell script for loading the assembly
- ///
- /// A PowerShell script for loading the assembly
- public static string GetPowerShellLoaderScript()
- {
- return @"
-function Load-WindowsNotifications {
- [CmdletBinding()]
- param (
- [Parameter(Mandatory=$false)]
- [string]$AssemblyPath
- )
-
- try {
- if ($AssemblyPath -and (Test-Path $AssemblyPath)) {
- # Load from file
- $bytes = [System.IO.File]::ReadAllBytes($AssemblyPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
- }
- else {
- # Load from embedded Base64 string
- $base64 = 'ASSEMBLY_BASE64_PLACEHOLDER'
- $bytes = [System.Convert]::FromBase64String($base64)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
- }
-
- # Return the assembly
- return $assembly
- }
- catch {
- Write-Error ""Failed to load WindowsNotifications assembly: $_""
- return $null
- }
-}
-
-# Example usage:
-# $assembly = Load-WindowsNotifications
-# $notificationManager = New-Object WindowsNotifications.NotificationManager
-# $options = New-Object WindowsNotifications.Models.NotificationOptions
-# $options.Title = ""Test Notification""
-# $options.Message = ""This is a test notification""
-# $result = $notificationManager.ShowNotification($options)
-";
}
}
}
diff --git a/WindowsNotifications/Utils/LiteDBEmbedded.cs b/WindowsNotifications/Utils/LiteDBEmbedded.cs
index f8803ae..e28df82 100644
--- a/WindowsNotifications/Utils/LiteDBEmbedded.cs
+++ b/WindowsNotifications/Utils/LiteDBEmbedded.cs
@@ -1,259 +1,48 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Reflection;
-using WindowsNotifications.Models;
namespace WindowsNotifications.Utils
{
///
- /// Provides embedded LiteDB functionality
+ /// Utility for working with embedded LiteDB.
///
- internal class LiteDBEmbedded
+ internal static class LiteDBEmbedded
{
- private readonly string _dbPath;
- private readonly Assembly _liteDbAssembly;
- private readonly Type _liteDatabase;
- private readonly Type _bsonMapper;
- private readonly Type _bsonDocument;
- private readonly Type _query;
- private readonly Type _bsonValue;
- private readonly object _db;
- private readonly object _mapper;
+ private static readonly object _lockObject = new object();
+ private static Assembly _liteDbAssembly;
///
- /// Creates a new LiteDBEmbedded instance with the specified database path
+ /// Gets a LiteDB database instance.
///
- /// The path to the database file
- public LiteDBEmbedded(string dbPath)
+ /// The path to the database file.
+ /// A LiteDB database instance.
+ public static dynamic GetDatabase(string databasePath)
{
- _dbPath = dbPath;
+ EnsureLiteDbLoaded();
- // Load the embedded LiteDB assembly
- _liteDbAssembly = LoadLiteDbAssembly();
-
- // Get the types we need
- _liteDatabase = _liteDbAssembly.GetType("LiteDB.LiteDatabase");
- _bsonMapper = _liteDbAssembly.GetType("LiteDB.BsonMapper");
- _bsonDocument = _liteDbAssembly.GetType("LiteDB.BsonDocument");
- _query = _liteDbAssembly.GetType("LiteDB.Query");
- _bsonValue = _liteDbAssembly.GetType("LiteDB.BsonValue");
-
- // Create the mapper
- _mapper = _bsonMapper.GetProperty("Global", BindingFlags.Public | BindingFlags.Static).GetValue(null);
-
- // Create the database
- _db = Activator.CreateInstance(_liteDatabase, new object[] { _dbPath });
-
- // Register the NotificationResult type
- RegisterNotificationResultType();
+ Type liteDbType = _liteDbAssembly.GetType("LiteDB.LiteDatabase");
+ return Activator.CreateInstance(liteDbType, databasePath);
}
- ///
- /// Loads the embedded LiteDB assembly
- ///
- /// The LiteDB assembly
- private Assembly LoadLiteDbAssembly()
+ private static void EnsureLiteDbLoaded()
{
- try
+ if (_liteDbAssembly != null)
{
- // Try to load from embedded resource
- string resourceName = "WindowsNotifications.Resources.LiteDB.dll";
- using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
+ return;
+ }
+
+ lock (_lockObject)
+ {
+ if (_liteDbAssembly != null)
{
- if (stream != null)
- {
- byte[] assemblyData = new byte[stream.Length];
- stream.Read(assemblyData, 0, assemblyData.Length);
- return Assembly.Load(assemblyData);
- }
+ return;
}
- // If not found as embedded resource, try to load from file
- return Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "LiteDB.dll"));
+ // Load the embedded LiteDB assembly
+ byte[] liteDbBytes = AssemblyLoader.GetEmbeddedResourceBytes("WindowsNotifications.Resources.LiteDB.dll");
+ _liteDbAssembly = Assembly.Load(liteDbBytes);
}
- catch (Exception ex)
- {
- throw new Exception("Failed to load LiteDB assembly", ex);
- }
- }
-
- ///
- /// Registers the NotificationResult type with the BsonMapper
- ///
- private void RegisterNotificationResultType()
- {
- try
- {
- // Get the Entity method
- MethodInfo entityMethod = _bsonMapper.GetMethod("Entity", new Type[] { });
-
- // Create a generic Entity method
- MethodInfo genericEntityMethod = entityMethod.MakeGenericMethod(typeof(NotificationResult));
-
- // Call the method to register the type
- genericEntityMethod.Invoke(_mapper, null);
- }
- catch (Exception ex)
- {
- throw new Exception("Failed to register NotificationResult type", ex);
- }
- }
-
- ///
- /// Inserts or updates a notification result in the database
- ///
- /// The notification result to save
- /// True if the operation was successful, false otherwise
- public bool UpsertNotificationResult(NotificationResult result)
- {
- try
- {
- // Get the collection
- object collection = _liteDatabase.GetMethod("GetCollection", new Type[] { typeof(string) })
- .Invoke(_db, new object[] { "NotificationResults" });
-
- // Convert the result to a BsonDocument
- object bsonDoc = _bsonMapper.GetMethod("ToDocument", new Type[] { typeof(object) })
- .Invoke(_mapper, new object[] { result });
-
- // Upsert the document
- bool success = (bool)collection.GetType().GetMethod("Upsert", new Type[] { _bsonDocument })
- .Invoke(collection, new object[] { bsonDoc });
-
- return success;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- ///
- /// Gets a notification result from the database
- ///
- /// The ID of the notification
- /// The notification result, or null if not found
- public NotificationResult GetNotificationResult(string notificationId)
- {
- try
- {
- // Get the collection
- object collection = _liteDatabase.GetMethod("GetCollection", new Type[] { typeof(string) })
- .Invoke(_db, new object[] { "NotificationResults" });
-
- // Create a query for the notification ID
- object query = collection.GetType().GetMethod("FindById")
- .Invoke(collection, new object[] { CreateBsonValue(notificationId) });
-
- // Convert the result to a NotificationResult
- if (query != null)
- {
- return (NotificationResult)_bsonMapper.GetMethod("ToObject", new Type[] { typeof(Type), _bsonDocument })
- .Invoke(_mapper, new object[] { typeof(NotificationResult), query });
- }
-
- return null;
- }
- catch (Exception)
- {
- return null;
- }
- }
-
- ///
- /// Gets all notification results from the database
- ///
- /// A list of notification results
- public List GetAllNotificationResults()
- {
- try
- {
- // Get the collection
- object collection = _liteDatabase.GetMethod("GetCollection", new Type[] { typeof(string) })
- .Invoke(_db, new object[] { "NotificationResults" });
-
- // Find all documents
- object query = collection.GetType().GetMethod("FindAll")
- .Invoke(collection, null);
-
- // Convert the results to a list of NotificationResults
- var results = new List();
-
- // Enumerate the results
- foreach (object doc in (System.Collections.IEnumerable)query)
- {
- var result = (NotificationResult)_bsonMapper.GetMethod("ToObject", new Type[] { typeof(Type), _bsonDocument })
- .Invoke(_mapper, new object[] { typeof(NotificationResult), doc });
-
- results.Add(result);
- }
-
- return results;
- }
- catch (Exception)
- {
- return new List();
- }
- }
-
- ///
- /// Deletes a notification result from the database
- ///
- /// The ID of the notification
- /// True if the deletion was successful, false otherwise
- public bool DeleteNotificationResult(string notificationId)
- {
- try
- {
- // Get the collection
- object collection = _liteDatabase.GetMethod("GetCollection", new Type[] { typeof(string) })
- .Invoke(_db, new object[] { "NotificationResults" });
-
- // Delete the document
- bool success = (bool)collection.GetType().GetMethod("Delete", new Type[] { _bsonValue })
- .Invoke(collection, new object[] { CreateBsonValue(notificationId) });
-
- return success;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- ///
- /// Deletes all notification results from the database
- ///
- /// True if the deletion was successful, false otherwise
- public bool DeleteAllNotificationResults()
- {
- try
- {
- // Get the collection
- object collection = _liteDatabase.GetMethod("GetCollection", new Type[] { typeof(string) })
- .Invoke(_db, new object[] { "NotificationResults" });
-
- // Delete all documents
- int count = (int)collection.GetType().GetMethod("DeleteAll")
- .Invoke(collection, null);
-
- return count > 0;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- ///
- /// Creates a BsonValue from a string
- ///
- /// The string value
- /// A BsonValue object
- private object CreateBsonValue(string value)
- {
- return Activator.CreateInstance(_bsonValue, new object[] { value });
}
}
}
diff --git a/WindowsNotifications/WindowsNotifications.csproj b/WindowsNotifications/WindowsNotifications.csproj
index a757791..1a536ed 100644
--- a/WindowsNotifications/WindowsNotifications.csproj
+++ b/WindowsNotifications/WindowsNotifications.csproj
@@ -33,29 +33,42 @@
-
- False
- C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Runtime.WindowsRuntime.dll
- True
-
-
- C:\Windows\Microsoft.NET\Framework\v4.0.30319\Windows.winmd
- False
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
@@ -63,8 +76,5 @@
-
-
-
diff --git a/build.ps1 b/build.ps1
index 8cd6149..846d89e 100644
--- a/build.ps1
+++ b/build.ps1
@@ -1,37 +1,79 @@
-# Build script for WindowsNotifications
+#
+# build.ps1
+# Build script for the Windows Notifications library
+#
+
+param (
+ [switch]$Clean,
+ [switch]$Release,
+ [switch]$Test
+)
# Set the configuration
-$Configuration = "Release"
+$configuration = if ($Release) { "Release" } else { "Debug" }
-# Check if dotnet CLI is available
-$dotnetPath = Get-Command dotnet -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
-if (-not $dotnetPath) {
- Write-Host "The .NET SDK is not installed or not in the PATH. Please install the .NET SDK." -ForegroundColor Red
+# Set the MSBuild path
+$msbuildPath = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe"
+if (-not (Test-Path $msbuildPath)) {
+ Write-Error "MSBuild not found at $msbuildPath"
exit 1
}
-# Clean the solution
-Write-Host "Cleaning solution..." -ForegroundColor Cyan
-dotnet clean WindowsNotifications.sln --configuration $Configuration
-
-# Restore NuGet packages
-Write-Host "Restoring NuGet packages..." -ForegroundColor Cyan
-dotnet restore WindowsNotifications.sln
+# Clean the solution if requested
+if ($Clean) {
+ Write-Host "Cleaning solution..."
+ & $msbuildPath "WindowsNotifications.sln" /t:Clean /p:Configuration=$configuration /v:minimal
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to clean solution"
+ exit 1
+ }
+}
# Build the solution
-Write-Host "Building solution..." -ForegroundColor Cyan
-dotnet build WindowsNotifications.sln --configuration $Configuration --no-restore
-
-# Check if the build was successful
-if ($LASTEXITCODE -eq 0) {
- Write-Host "Build completed successfully!" -ForegroundColor Green
-
- # Show the output directory
- $outputDir = Join-Path -Path $PSScriptRoot -ChildPath "WindowsNotifications\bin\$Configuration"
- Write-Host "Output directory: $outputDir" -ForegroundColor Yellow
-
- # List the files in the output directory
- Get-ChildItem -Path $outputDir | Format-Table Name, Length
-} else {
- Write-Host "Build failed with exit code $LASTEXITCODE" -ForegroundColor Red
+Write-Host "Building solution in $configuration configuration..."
+& $msbuildPath "WindowsNotifications.sln" /t:Build /p:Configuration=$configuration /v:minimal
+if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to build solution"
+ exit 1
}
+
+# Run tests if requested
+if ($Test) {
+ Write-Host "Running tests..."
+ $testDll = "WindowsNotifications.Tests\bin\$configuration\WindowsNotifications.Tests.dll"
+ if (-not (Test-Path $testDll)) {
+ Write-Error "Test DLL not found at $testDll"
+ exit 1
+ }
+
+ # Try to find NUnit console runner
+ $nunitPath = "packages\NUnit.ConsoleRunner\tools\nunit3-console.exe"
+ if (-not (Test-Path $nunitPath)) {
+ Write-Host "NUnit console runner not found, installing..."
+ & nuget install NUnit.ConsoleRunner -OutputDirectory packages
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Failed to install NUnit console runner"
+ exit 1
+ }
+
+ $nunitPath = (Get-ChildItem -Path "packages" -Filter "nunit3-console.exe" -Recurse).FullName
+ if (-not $nunitPath) {
+ Write-Error "NUnit console runner not found after installation"
+ exit 1
+ }
+ }
+
+ & $nunitPath $testDll
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "Tests failed"
+ exit 1
+ }
+}
+
+# Copy the DLL to the PowerShell module directory
+Write-Host "Copying DLL to PowerShell module directory..."
+$dllPath = "WindowsNotifications\bin\$configuration\WindowsNotifications.dll"
+$modulePath = "PowerShell\WindowsNotifications.dll"
+Copy-Item -Path $dllPath -Destination $modulePath -Force
+
+Write-Host "Build completed successfully"
diff --git a/check-assembly.ps1 b/check-assembly.ps1
new file mode 100644
index 0000000..92273d1
--- /dev/null
+++ b/check-assembly.ps1
@@ -0,0 +1,3 @@
+Add-Type -Path 'WindowsNotifications\bin\Release\WindowsNotifications.dll'
+$assembly = [AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -eq 'WindowsNotifications' }
+$assembly.GetTypes() | Select-Object FullName
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..e854aa9
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,9 @@
+# Documentation
+
+This directory contains documentation for the Windows Notifications project.
+
+## Contents
+
+- `architecture.md` - Overview of the system architecture
+- `api.md` - API documentation
+- `usage.md` - Usage examples and best practices
diff --git a/run-tests.ps1 b/run-tests.ps1
deleted file mode 100644
index a8b5b09..0000000
--- a/run-tests.ps1
+++ /dev/null
@@ -1,33 +0,0 @@
-# Run tests for SimpleWindowsNotifications
-
-# Set the configuration
-$Configuration = "Release"
-
-# Check if dotnet CLI is available
-$dotnetPath = Get-Command dotnet -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
-if (-not $dotnetPath) {
- Write-Host "The .NET SDK is not installed or not in the PATH. Please install the .NET SDK." -ForegroundColor Red
- exit 1
-}
-
-# Build the solution
-Write-Host "Building solution..." -ForegroundColor Cyan
-dotnet build SimpleWindowsNotifications.sln --configuration $Configuration
-
-# Check if the build was successful
-if ($LASTEXITCODE -ne 0) {
- Write-Host "Build failed with exit code $LASTEXITCODE" -ForegroundColor Red
- exit $LASTEXITCODE
-}
-
-# Run the tests
-Write-Host "Running tests..." -ForegroundColor Cyan
-dotnet test SimpleWindowsNotifications.sln --configuration $Configuration --no-build
-
-# Check if the tests were successful
-if ($LASTEXITCODE -eq 0) {
- Write-Host "All tests passed!" -ForegroundColor Green
-} else {
- Write-Host "Tests failed with exit code $LASTEXITCODE" -ForegroundColor Red
- exit $LASTEXITCODE
-}
diff --git a/simple-build.ps1 b/simple-build.ps1
deleted file mode 100644
index f80fdc0..0000000
--- a/simple-build.ps1
+++ /dev/null
@@ -1,37 +0,0 @@
-# Build script for SimpleWindowsNotifications
-
-# Set the configuration
-$Configuration = "Release"
-
-# Check if dotnet CLI is available
-$dotnetPath = Get-Command dotnet -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source
-if (-not $dotnetPath) {
- Write-Host "The .NET SDK is not installed or not in the PATH. Please install the .NET SDK." -ForegroundColor Red
- exit 1
-}
-
-# Clean the solution
-Write-Host "Cleaning solution..." -ForegroundColor Cyan
-dotnet clean SimpleWindowsNotifications.sln --configuration $Configuration
-
-# Restore NuGet packages
-Write-Host "Restoring NuGet packages..." -ForegroundColor Cyan
-dotnet restore SimpleWindowsNotifications.sln
-
-# Build the solution
-Write-Host "Building solution..." -ForegroundColor Cyan
-dotnet build SimpleWindowsNotifications.sln --configuration $Configuration --no-restore
-
-# Check if the build was successful
-if ($LASTEXITCODE -eq 0) {
- Write-Host "Build completed successfully!" -ForegroundColor Green
-
- # Show the output directory
- $outputDir = Join-Path -Path $PSScriptRoot -ChildPath "WindowsNotifications\bin\$Configuration"
- Write-Host "Output directory: $outputDir" -ForegroundColor Yellow
-
- # List the files in the output directory
- Get-ChildItem -Path $outputDir | Format-Table Name, Length
-} else {
- Write-Host "Build failed with exit code $LASTEXITCODE" -ForegroundColor Red
-}
diff --git a/src/README.md b/src/README.md
new file mode 100644
index 0000000..3d4d337
--- /dev/null
+++ b/src/README.md
@@ -0,0 +1,8 @@
+# Source Code
+
+This directory contains the source code for the Windows Notifications project.
+
+## Structure
+
+- `NotificationApp/` - The WPF application for displaying notifications
+- `PowerShell/` - PowerShell module for integrating with the notification system
diff --git a/test-simple.ps1 b/test-simple.ps1
deleted file mode 100644
index 9e00b4f..0000000
--- a/test-simple.ps1
+++ /dev/null
@@ -1,50 +0,0 @@
-# Test script for SimpleWindowsNotifications
-
-# Load the WindowsNotifications assembly
-$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "WindowsNotifications\bin\Release\WindowsNotifications.dll"
-if (Test-Path $dllPath) {
- $bytes = [System.IO.File]::ReadAllBytes($dllPath)
- $assembly = [System.Reflection.Assembly]::Load($bytes)
- Write-Host "Assembly loaded: $($assembly.FullName)" -ForegroundColor Green
-} else {
- Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
- exit
-}
-
-# Create a notification manager
-$notificationManager = New-Object WindowsNotifications.SimpleNotificationManager
-Write-Host "NotificationManager created" -ForegroundColor Green
-
-# Show a simple notification
-Write-Host "Showing a simple notification..." -ForegroundColor Cyan
-$result = $notificationManager.ShowSimpleNotification("Hello, World!", "This is a test notification from the SimpleWindowsNotifications library.")
-Write-Host "Notification displayed: $($result.Displayed)" -ForegroundColor Yellow
-Write-Host "Notification ID: $($result.NotificationId)" -ForegroundColor Yellow
-
-# Show a notification with buttons
-Write-Host "`nShowing a notification with buttons..." -ForegroundColor Cyan
-$result = $notificationManager.ShowNotificationWithButtons("Notification with Buttons", "Please select an option:", "OK", "Cancel")
-Write-Host "Notification displayed: $($result.Displayed)" -ForegroundColor Yellow
-Write-Host "Button clicked: $($result.ClickedButtonText)" -ForegroundColor Yellow
-
-# Show a custom notification
-Write-Host "`nShowing a custom notification..." -ForegroundColor Cyan
-$options = New-Object WindowsNotifications.Models.NotificationOptions
-$options.Title = "Custom Notification"
-$options.Message = "This is a custom notification with options."
-$options.Async = $true
-$options.EnableLogging = $true
-$options.LogAction = { param($logEntry) Write-Host $logEntry -ForegroundColor Gray }
-
-$button1 = New-Object WindowsNotifications.Models.NotificationButton("Yes", "yes", "yes-arg")
-$button2 = New-Object WindowsNotifications.Models.NotificationButton("No", "no", "no-arg")
-$button3 = New-Object WindowsNotifications.Models.NotificationButton("Maybe", "maybe", "maybe-arg")
-$options.Buttons.Add($button1)
-$options.Buttons.Add($button2)
-$options.Buttons.Add($button3)
-
-$result = $notificationManager.ShowNotification($options)
-Write-Host "Notification displayed: $($result.Displayed)" -ForegroundColor Yellow
-Write-Host "Notification ID: $($result.NotificationId)" -ForegroundColor Yellow
-
-Write-Host "`nTest completed successfully!" -ForegroundColor Green