Add PowerShell cmdlets and update project structure

This commit is contained in:
GraceSolutions
2025-04-10 21:13:24 -04:00
parent e5a7f10ade
commit 7cd5962115
56 changed files with 2356 additions and 3914 deletions
+35 -59
View File
@@ -1,84 +1,60 @@
# Example: Asynchronous Notification # 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" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { Initialize-WindowsNotifications -DllPath $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 # Show an asynchronous notification
$notificationManager = New-Object WindowsNotifications.NotificationManager $result = Show-Notification -Title "Background Task" -Message "A background task is running..." -Buttons "Cancel", "View Details" -Async
# Create custom notification options # Display the initial result
$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
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# Simulate doing some work # Simulate a background task
Write-Host "Performing background work..." Write-Host "Performing background task..."
for ($i = 1; $i -le 5; $i++) { for ($i = 1; $i -le 5; $i++) {
Write-Host " Working... ($i/5)" Write-Host "Working... ($i/5)"
Start-Sleep -Seconds 2 Start-Sleep -Seconds 2
# Check if the notification has been interacted with # 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 -and ($currentResult.Activated -or $currentResult.Dismissed)) {
if ($currentResult.ClickedButtonId -eq "cancel") { Write-Host "User interacted with the notification"
Write-Host "User canceled the task!"
break if ($currentResult.ClickedButtonId) {
} Write-Host "Button clicked: $($currentResult.ClickedButtonText) (ID: $($currentResult.ClickedButtonId))"
elseif ($currentResult.ClickedButtonId -eq "view") {
Write-Host "User viewed the progress!"
# Show a new notification with updated progress # Handle button clicks
$progressOptions = New-Object WindowsNotifications.Models.NotificationOptions if ($currentResult.ClickedButtonText -eq "Cancel") {
$progressOptions.Title = "Task Progress" Write-Host "User cancelled the task"
$progressOptions.Message = "Progress: $i/5 steps completed" break
$progressOptions.Tag = "progress" # Group with the same tag } elseif ($currentResult.ClickedButtonText -eq "View Details") {
$notificationManager.ShowNotification($progressOptions) 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 # Get the final result
$completionOptions = New-Object WindowsNotifications.Models.NotificationOptions $finalResult = Get-NotificationResult -NotificationId $result.NotificationId
$completionOptions.Title = "Task Completed" if ($finalResult) {
$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) {
Write-Host "Final notification state:" Write-Host "Final notification state:"
Write-Host " Activated: $($finalResult.Activated)" Write-Host " Activated: $($finalResult.Activated)"
Write-Host " Dismissed: $($finalResult.Dismissed)" Write-Host " Dismissed: $($finalResult.Dismissed)"
if ($finalResult.ClickedButtonId) { if ($finalResult.ClickedButtonId) {
Write-Host " Button clicked: $($finalResult.ClickedButtonText)" Write-Host " Button clicked: $($finalResult.ClickedButtonText) (ID: $($finalResult.ClickedButtonId))"
} }
} }
+32 -113
View File
@@ -1,125 +1,44 @@
# Example: Countdown and Deadline Notification # 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" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { Initialize-WindowsNotifications -DllPath $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 # Set a deadline 2 minutes from now
$notificationManager = New-Object WindowsNotifications.NotificationManager $deadline = (Get-Date).AddMinutes(2)
# Create custom notification options # Show a countdown notification
$options = New-Object WindowsNotifications.Models.NotificationOptions $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"
$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)
# Display the result # Display the result
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# Wait for the deadline to pass # If the notification was interacted with, show the details
Write-Host "Waiting for user interaction or deadline to pass..." if ($result.Activated) {
$waitTime = [int]([Math]::Ceiling(($options.DeadlineTime - (Get-Date)).TotalSeconds)) + 5 Write-Host "Notification was activated"
Start-Sleep -Seconds $waitTime if ($result.ClickedButtonId) {
Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
# Get the final result
$finalResult = $notificationManager.GetNotificationResult($result.NotificationId) # Handle button clicks
if ($finalResult -ne $null) { if ($result.ClickedButtonText -eq "Restart Now") {
Write-Host "`nFinal notification state:" Write-Host "User chose to restart now (simulated)"
Write-Host " Activated: $($finalResult.Activated)" # In a real script, you would restart the computer here
Write-Host " Dismissed: $($finalResult.Dismissed)" # Restart-Computer -Force
Write-Host " Deferred: $($finalResult.Deferred)" } elseif ($result.ClickedButtonText -eq "Remind Me Later") {
Write-Host "User chose to be reminded later"
if ($finalResult.Deferred) { # In a real script, you would schedule a reminder here
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)"
} }
} 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"
+31 -74
View File
@@ -1,105 +1,62 @@
# Example: Custom Branded Notification # 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" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { Initialize-WindowsNotifications -DllPath $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 # Create custom notification options
$notificationManager = New-Object WindowsNotifications.NotificationManager
# Create custom notification options with branding
$options = New-Object WindowsNotifications.Models.NotificationOptions $options = New-Object WindowsNotifications.Models.NotificationOptions
# Set basic notification properties
$options.Title = "IT Department Notification" $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 # Set branding properties
$options.BrandingText = "Contoso IT Department" $options.Attribution = "Contoso IT Department"
$options.BrandingColor = "#0078D7" # Blue
$options.AccentColor = "#E81123" # Red
$options.UseDarkTheme = $true
# Set custom images # Set custom images (file paths or URLs)
# Note: Replace these paths with actual image paths on your system $logoPath = Join-Path -Path $PSScriptRoot -ChildPath "logo.png"
$logoPath = Join-Path -Path $PSScriptRoot -ChildPath "Images\company-logo.png"
if (Test-Path $logoPath) { if (Test-Path $logoPath) {
$options.LogoImagePath = $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) { if (Test-Path $heroPath) {
$options.HeroImagePath = $heroPath $options.HeroImagePath = $heroPath
} }
# Add custom buttons with images # Add custom buttons
$updateButton = New-Object WindowsNotifications.Models.NotificationButton("Install Update", "install") $updateButton = New-Object WindowsNotifications.Models.NotificationButton("Install Update", "install")
$updateButton.BackgroundColor = "#107C10" # Green $laterButton = New-Object WindowsNotifications.Models.NotificationButton("Later", "later")
$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
$options.Buttons.Add($updateButton) $options.Buttons.Add($updateButton)
$options.Buttons.Add($deferButton) $options.Buttons.Add($laterButton)
$options.Buttons.Add($moreInfoButton)
# Configure audio
$options.AudioSource = "ms-winsoundevent:Notification.Default"
$options.SilentMode = $false
# Show the notification # Show the notification
Write-Host "Showing custom branded notification..." $notificationManager = New-Object WindowsNotifications.NotificationManager
$result = $notificationManager.ShowNotification($options) $result = $notificationManager.ShowNotification($options)
# Display the result # Display the result
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# If the notification was displayed, wait for interaction # If the notification was interacted with, show the details
if ($result.Displayed) { if ($result.Activated) {
Write-Host "Waiting for user interaction..." Write-Host "Notification was activated"
$result = $notificationManager.WaitForNotification($result.NotificationId) if ($result.ClickedButtonId) {
Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
if ($result -ne $null) {
Write-Host "Notification was activated: $($result.Activated)"
Write-Host "Notification was dismissed: $($result.Dismissed)"
if ($result.ClickedButtonId) { # Handle button clicks
Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))" if ($result.ClickedButtonId -eq "install") {
Write-Host "User chose to install the update (simulated)"
# Take action based on which button was clicked # In a real script, you would install the update here
switch ($result.ClickedButtonId) { } elseif ($result.ClickedButtonId -eq "later") {
"install" { Write-Host "User chose to install later"
Write-Host "User chose to install the update" # In a real script, you would schedule a reminder here
# 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
}
}
} }
} }
} elseif ($result.Dismissed) {
Write-Host "Notification was dismissed"
} }
+25 -31
View File
@@ -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 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 # First, convert the DLL to a Base64 string
# For demonstration purposes, we'll load the DLL and convert it to Base64 # In a real scenario, you would have this string pre-generated
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { $bytes = [System.IO.File]::ReadAllBytes($dllPath)
$bytes = [System.IO.File]::ReadAllBytes($dllPath) $base64 = [Convert]::ToBase64String($bytes)
$base64 = [Convert]::ToBase64String($bytes)
# Now, load the assembly from the Base64 string
Write-Host "Assembly loaded and converted to Base64" $assemblyBytes = [Convert]::FromBase64String($base64)
Write-Host "Base64 string length: $($base64.Length) characters" $assembly = [System.Reflection.Assembly]::Load($assemblyBytes)
# In a real script, the base64 string would be hardcoded here # Create a notification manager
# $base64 = "YOUR_BASE64_STRING_HERE" $notificationManager = New-Object WindowsNotifications.NotificationManager
# Load the assembly from the Base64 string # Show a simple notification
$assemblyBytes = [Convert]::FromBase64String($base64) $result = $notificationManager.ShowSimpleNotification("Loaded from Base64", "This notification was shown using an assembly loaded from a Base64 string.")
$assembly = [System.Reflection.Assembly]::Load($assemblyBytes)
# Display the result
Write-Host "Assembly loaded successfully: $($assembly.FullName)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)"
# Create a notification manager and show a simple notification
$notificationManager = New-Object WindowsNotifications.NotificationManager # If the notification was interacted with, show the details
$result = $notificationManager.ShowSimpleNotification( if ($result.Activated) {
"Loaded from Base64", Write-Host "Notification was activated"
"This notification was shown from an assembly loaded from a Base64 string" } elseif ($result.Dismissed) {
) Write-Host "Notification was dismissed"
Write-Host "Notification displayed: $($result.Displayed)"
}
else {
Write-Error "WindowsNotifications.dll not found at $dllPath. Please build the solution first."
} }
+14 -45
View File
@@ -1,58 +1,27 @@
# Example: Notification with Buttons # Example: Notification with Buttons
# This script demonstrates how to show a notification with buttons # This script demonstrates how to show a notification with buttons
# Load the WindowsNotifications assembly # Import the module
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll" $modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
if (Test-Path $dllPath) { Import-Module $modulePath -Force
$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 # Initialize the notification system
$notificationManager = New-Object WindowsNotifications.NotificationManager $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
Initialize-WindowsNotifications -DllPath $dllPath
# Show a notification with buttons # Show a notification with buttons
Write-Host "Showing notification with buttons..." $result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer"
$result = $notificationManager.ShowNotificationWithButtons(
"Action Required",
"Please select an option below:",
"Approve",
"Reject",
"Defer"
)
# Display the result # Display the result
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# If the notification was displayed, wait for interaction # If the notification was interacted with, show the details
if ($result.Displayed) { if ($result.Activated) {
Write-Host "Waiting for user interaction..." Write-Host "Notification was activated"
$result = $notificationManager.WaitForNotification($result.NotificationId) if ($result.ClickedButtonId) {
Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
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"
}
}
}
} }
} elseif ($result.Dismissed) {
Write-Host "Notification was dismissed"
} }
+19 -92
View File
@@ -1,21 +1,13 @@
# Example: Using the WindowsNotifications PowerShell Module # Example: PowerShell Module
# This script demonstrates how to use the WindowsNotifications PowerShell module # This script demonstrates how to use the PowerShell module
# Import the module # Import the module
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell" $modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell\WindowsNotifications.psd1"
Import-Module $modulePath -Force Import-Module $modulePath -Force
# Initialize the module # Initialize the notification system
# Note: The DLL must be in the same directory as the module files
$dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { Initialize-WindowsNotifications -DllPath $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
}
# Check if running as SYSTEM # Check if running as SYSTEM
$isSystem = Test-SystemContext $isSystem = Test-SystemContext
@@ -23,94 +15,29 @@ Write-Host "Running as SYSTEM: $isSystem"
# Get interactive user sessions # Get interactive user sessions
$sessions = Get-InteractiveUserSessions $sessions = Get-InteractiveUserSessions
Write-Host "Interactive user sessions found: $($sessions.Count)" Write-Host "Interactive user sessions: $($sessions.Count)"
foreach ($session in $sessions) { foreach ($session in $sessions) {
Write-Host " $session" Write-Host " - $session"
} }
# Show a simple notification # Show a simple notification
Write-Host "`nShowing simple notification..." $result = Show-Notification -Title "PowerShell Module" -Message "This notification was shown using the PowerShell module."
$result = Show-Notification -Title "Simple Notification" -Message "This is a simple notification from the PowerShell module"
# Display the result # Display the result
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# Show a notification with buttons # If the notification was interacted with, show the details
Write-Host "`nShowing notification with buttons..." if ($result.Activated) {
$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer" Write-Host "Notification was activated"
} elseif ($result.Dismissed) {
# Display the result Write-Host "Notification was dismissed"
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" }
}
} }
# Show a reboot notification # Get all notification results
Write-Host "`nShowing reboot notification..." $results = Get-AllNotificationResults
$result = Show-Notification -Title "System Reboot Required" -Message "Your system needs to be rebooted to complete updates." -RebootButtonText "Reboot Now" -DeferButtonText "Defer" Write-Host "Total notification results: $($results.Count)"
# Display the result # Clear all notification results
Write-Host "Notification displayed: $($result.Displayed)" Clear-AllNotificationResults
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "All notification results cleared"
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"
+42
View File
@@ -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"
+10
View File
@@ -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
+21 -53
View File
@@ -1,68 +1,36 @@
# Example: Reboot Notification with Deferrals # Example: Reboot Notification
# This script demonstrates how to show a reboot notification with deferral options # 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" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { Initialize-WindowsNotifications -DllPath $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 # Show a reboot notification
$notificationManager = New-Object WindowsNotifications.NotificationManager $result = Show-Notification -Title "System Reboot Required" -Message "Your system needs to be rebooted to complete updates." -RebootButtonText "Reboot Now" -DeferButtonText "Defer"
# 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)
# Display the result # Display the result
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# If the notification was displayed, wait for interaction # If the notification was interacted with, show the details
if ($result.Displayed) { if ($result.Activated) {
Write-Host "Waiting for user interaction..." Write-Host "Notification was activated"
$result = $notificationManager.WaitForNotification($result.NotificationId) if ($result.ClickedButtonId) {
Write-Host "Button clicked: $($result.ClickedButtonText) (ID: $($result.ClickedButtonId))"
if ($result -ne $null) {
Write-Host "Notification was activated: $($result.Activated)"
Write-Host "Notification was dismissed: $($result.Dismissed)"
# Handle button clicks
if ($result.ClickedButtonId -eq "reboot") { if ($result.ClickedButtonId -eq "reboot") {
Write-Host "User chose to reboot now" Write-Host "User chose to reboot now (simulated)"
# In a real script, you would initiate a reboot here # In a real script, you would reboot the computer here
# Restart-Computer -Force # 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)"
} }
+12 -37
View File
@@ -1,49 +1,24 @@
# Example: Simple Notification # Example: Simple Notification
# This script demonstrates how to show a 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" $dllPath = Join-Path -Path $PSScriptRoot -ChildPath "..\WindowsNotifications\bin\Release\WindowsNotifications.dll"
if (Test-Path $dllPath) { Initialize-WindowsNotifications -DllPath $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"
}
# Show a simple notification # Show a simple notification
Write-Host "Showing simple notification..." $result = Show-Notification -Title "Simple Notification" -Message "This is a simple notification." -TimeoutInSeconds 10
$result = $notificationManager.ShowSimpleNotification("Simple Notification", "This is a simple notification from PowerShell")
# Display the result # Display the result
Write-Host "Notification displayed: $($result.Displayed)" Write-Host "Notification displayed: $($result.Displayed)"
Write-Host "Notification ID: $($result.NotificationId)" Write-Host "Notification ID: $($result.NotificationId)"
# If the notification was displayed, wait for interaction # If the notification was interacted with, show the details
if ($result.Displayed) { if ($result.Activated) {
Write-Host "Waiting for user interaction..." Write-Host "Notification was activated"
$result = $notificationManager.WaitForNotification($result.NotificationId, 30000) } elseif ($result.Dismissed) {
Write-Host "Notification was dismissed"
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"
}
} }
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
+18 -259
View File
@@ -1,20 +1,26 @@
# Windows Notifications # 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 ## Features
- **User Context Notifications**: Display notifications in the user context from SYSTEM using user impersonation - **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 - **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 - **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) - **Deferral Support**: Allow users to defer notifications (e.g., for system reboots)
- **State Persistence**: Save notification state using embedded LiteDB - **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 - **Synchronous/Asynchronous Modes**: Run notifications in blocking or non-blocking mode
- **Countdown Display**: Show countdown timers for time-sensitive notifications - **Countdown Display**: Show countdown timers for time-sensitive notifications
- **Deadline Actions**: Configure custom actions to execute when notification deadlines are reached - **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 ## 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 ```powershell
# Create a notification manager # Import the module
$notificationManager = New-Object WindowsNotifications.NotificationManager 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<NotificationResult> 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<string> 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<NotificationButton> 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<string> LogAction` - Optional action to handle logging
- `Action<NotificationResult> OnActivated` - Optional action to execute when the notification is activated
- `Action<NotificationResult> OnTimeout` - Optional action to execute when the notification times out
- `Action<NotificationResult> 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 the module
Initialize-WindowsNotifications Initialize-WindowsNotifications
@@ -342,9 +105,6 @@ Show-Notification -Title "Hello" -Message "This is a simple notification"
# Show a notification with buttons # Show a notification with buttons
$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer" $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. 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 ## Building from Source
1. Clone the repository 1. Clone the repository
2. Open the solution in Visual Studio 2. Run the build script: `./build.ps1 -Release`
3. Restore NuGet packages 3. The compiled DLL will be in the `WindowsNotifications\bin\Release` directory
4. Build the solution in Release mode 4. The PowerShell module will be in the `PowerShell` directory
5. The compiled DLL will be in the `WindowsNotifications\bin\Release` directory
## License ## License
-30
View File
@@ -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
@@ -1,6 +1,7 @@
using NUnit.Framework; using NUnit.Framework;
using System; using System;
using System.IO; using System.IO;
using WindowsNotifications;
using WindowsNotifications.Models; using WindowsNotifications.Models;
namespace WindowsNotifications.Tests namespace WindowsNotifications.Tests
@@ -8,203 +9,165 @@ namespace WindowsNotifications.Tests
[TestFixture] [TestFixture]
public class NotificationManagerTests public class NotificationManagerTests
{ {
[Test] private string _testDbPath;
public void Constructor_SetsDefaultDatabasePath() private NotificationManager _manager;
{
// Act
var manager = new SimpleNotificationManager();
// Assert [SetUp]
Assert.IsNotNull(manager.DatabasePath); public void Setup()
Assert.IsTrue(manager.DatabasePath.Contains("WindowsNotifications")); {
Assert.IsTrue(manager.DatabasePath.EndsWith("notifications.db")); _testDbPath = Path.Combine(Path.GetTempPath(), string.Format("test_notifications_{0}.db", Guid.NewGuid()));
_manager = new NotificationManager(_testDbPath);
} }
[Test] [TearDown]
public void Constructor_SetsCustomDatabasePath() public void TearDown()
{ {
// Arrange if (File.Exists(_testDbPath))
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<ArgumentNullException>(() => manager.ShowNotification(null));
var emptyOptions = new NotificationOptions();
Assert.Throws<ArgumentException>(() => manager.ShowNotification(emptyOptions));
}
[Test]
public void ShowNotification_ReturnsValidResult()
{
// Arrange
var manager = new SimpleNotificationManager();
var options = new NotificationOptions
{ {
Title = "Test Title", try
Message = "Test Message" {
}; File.Delete(_testDbPath);
}
// Act catch
var result = manager.ShowNotification(options); {
// Ignore errors
// Assert }
Assert.IsNotNull(result); }
Assert.AreEqual(options.Id, result.NotificationId);
Assert.IsTrue(result.Displayed);
Assert.IsNotNull(result.CreatedTime);
} }
[Test] [Test]
public void ShowSimpleNotification_ReturnsValidResult() public void NotificationManager_Constructor_DefaultPath_IsCorrect()
{ {
// Arrange // Arrange & Act
var manager = new SimpleNotificationManager(); var manager = new NotificationManager();
// Act
var result = manager.ShowSimpleNotification("Test Title", "Test Message");
// Assert // Assert
Assert.IsNotNull(result); string dbPath = manager.GetDatabaseFilePath();
Assert.IsNotNull(result.NotificationId); Assert.IsNotNull(dbPath);
Assert.IsTrue(result.Displayed); Assert.IsTrue(dbPath.Contains("WindowsNotifications"));
Assert.IsNotNull(result.CreatedTime); Assert.IsTrue(dbPath.EndsWith(".db"));
} }
[Test] [Test]
public void ShowNotificationWithButtons_ReturnsValidResult() public void NotificationManager_Constructor_CustomPath_IsCorrect()
{ {
// Arrange // Arrange & Act
var manager = new SimpleNotificationManager(); var customPath = Path.Combine(Path.GetTempPath(), "custom_notifications.db");
var manager = new NotificationManager(customPath);
// Act
var result = manager.ShowNotificationWithButtons("Test Title", "Test Message", "Button 1", "Button 2");
// Assert // Assert
Assert.IsNotNull(result); Assert.AreEqual(customPath, manager.GetDatabaseFilePath());
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);
} }
[Test] [Test]
public void GetDatabaseFilePath_ReturnsDatabasePath() public void NotificationManager_IsRunningAsSystem_ReturnsCorrectValue()
{ {
// Arrange // Arrange & Act
string customPath = Path.Combine(Path.GetTempPath(), "custom.db"); bool isSystem = _manager.IsRunningAsSystem();
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();
// Assert // Assert
// This will be false in a test environment, but we're just testing the method returns a value
Assert.IsFalse(isSystem); Assert.IsFalse(isSystem);
} }
[Test] [Test]
public void GetInteractiveUserSessions_ReturnsEmptyList() public void NotificationManager_GetInteractiveUserSessions_ReturnsNonNullList()
{ {
// Arrange // Arrange & Act
var manager = new SimpleNotificationManager(); var sessions = _manager.GetInteractiveUserSessions();
// Act
var sessions = manager.GetInteractiveUserSessions();
// Assert // Assert
Assert.IsNotNull(sessions); Assert.IsNotNull(sessions);
Assert.AreEqual(0, sessions.Count); // We can't assert the count because it depends on the environment
} }
[Test] [Test]
public void GetNotificationResult_ReturnsNull() public void NotificationManager_ShowSimpleNotification_ReturnsResult()
{ {
// Arrange // Arrange
var manager = new SimpleNotificationManager(); string title = "Test Title";
string message = "Test Message";
// Act // 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
Assert.IsNull(result); Assert.IsNull(result);
} }
[Test] [Test]
public void WaitForNotification_ReturnsNull() public void NotificationManager_GetAllNotificationResults_ReturnsNonNullList()
{ {
// Arrange // Arrange & Act
var manager = new SimpleNotificationManager(); var results = _manager.GetAllNotificationResults();
// 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();
// Assert // Assert
Assert.IsNotNull(results); Assert.IsNotNull(results);
Assert.AreEqual(0, results.Count);
} }
[Test] [Test]
public void DeleteNotificationResult_ReturnsTrue() public void NotificationManager_DeleteNotificationResult_ReturnsFalseForNonExistentId()
{ {
// Arrange // Arrange
var manager = new SimpleNotificationManager(); string nonExistentId = Guid.NewGuid().ToString();
// Act // Act
bool result = manager.DeleteNotificationResult("test-id"); bool result = _manager.DeleteNotificationResult(nonExistentId);
// Assert // Assert
Assert.IsTrue(result); Assert.IsFalse(result);
} }
[Test] [Test]
public void DeleteAllNotificationResults_ReturnsTrue() public void NotificationManager_DeleteAllNotificationResults_ReturnsTrue()
{ {
// Arrange // Arrange & Act
var manager = new SimpleNotificationManager(); bool result = _manager.DeleteAllNotificationResults();
// Act
bool result = manager.DeleteAllNotificationResults();
// Assert // Assert
Assert.IsTrue(result); Assert.IsTrue(result);
@@ -1,6 +1,5 @@
using NUnit.Framework; using NUnit.Framework;
using System; using System;
using System.Linq;
using WindowsNotifications.Models; using WindowsNotifications.Models;
namespace WindowsNotifications.Tests namespace WindowsNotifications.Tests
@@ -9,36 +8,41 @@ namespace WindowsNotifications.Tests
public class NotificationOptionsTests public class NotificationOptionsTests
{ {
[Test] [Test]
public void Constructor_SetsDefaultValues() public void NotificationOptions_DefaultValues_AreCorrect()
{ {
// Act // Arrange & Act
var options = new NotificationOptions(); var options = new NotificationOptions();
// Assert // Assert
Assert.IsNull(options.Title); Assert.IsNotNull(options.Buttons);
Assert.IsNull(options.Message); Assert.AreEqual(0, options.Buttons.Count);
Assert.IsNull(options.LogoImagePath);
Assert.IsNull(options.HeroImagePath);
Assert.IsNull(options.Attribution);
Assert.AreEqual(0, options.TimeoutInSeconds);
Assert.IsFalse(options.Async); Assert.IsFalse(options.Async);
Assert.IsNotNull(options.Id); Assert.IsNotNull(options.Id);
Assert.IsTrue(Guid.TryParse(options.Id, out _)); Assert.IsTrue(Guid.TryParse(options.Id, out _));
Assert.IsNull(options.Tag); Assert.IsTrue(options.PersistState);
Assert.IsNull(options.Group); Assert.AreEqual(60, options.ReminderTimeInMinutes);
Assert.IsFalse(options.PersistState); Assert.IsFalse(options.ShowReminder);
Assert.IsTrue(options.EnableLogging); Assert.IsFalse(options.ShowCountdown);
Assert.IsFalse(options.EnableLogging);
Assert.IsNull(options.LogAction); Assert.IsNull(options.LogAction);
Assert.IsNotNull(options.Buttons); Assert.IsNull(options.OnActivated);
Assert.AreEqual(0, options.Buttons.Count); Assert.IsNull(options.OnTimeout);
Assert.IsNull(options.OnError);
Assert.IsNull(options.DeadlineTime);
Assert.IsNull(options.DeadlineAction);
} }
[Test] [Test]
public void Properties_CanBeSet() public void NotificationOptions_SetProperties_ValuesAreCorrect()
{ {
// Arrange // Arrange
var options = new NotificationOptions(); var options = new NotificationOptions();
Action<string> logAction = (log) => { }; var button = new NotificationButton("Test", "test");
var deferralOptions = new DeferralOptions();
var deadlineAction = DeadlineAction.ExecuteCommand("test");
var deadlineTime = DateTime.Now.AddHours(1);
Action<string> logAction = (s) => { };
Action<NotificationResult> resultAction = (r) => { };
// Act // Act
options.Title = "Test Title"; options.Title = "Test Title";
@@ -47,13 +51,23 @@ namespace WindowsNotifications.Tests
options.HeroImagePath = "hero.png"; options.HeroImagePath = "hero.png";
options.Attribution = "Test Attribution"; options.Attribution = "Test Attribution";
options.TimeoutInSeconds = 30; options.TimeoutInSeconds = 30;
options.Buttons.Add(button);
options.Async = true; options.Async = true;
options.Id = "custom-id"; options.Id = "test-id";
options.Tag = "test-tag"; options.Tag = "test-tag";
options.Group = "test-group"; options.Group = "test-group";
options.PersistState = true; options.DeferralOptions = deferralOptions;
options.EnableLogging = false; options.ShowReminder = true;
options.ReminderTimeInMinutes = 15;
options.PersistState = false;
options.EnableLogging = true;
options.LogAction = logAction; options.LogAction = logAction;
options.OnActivated = resultAction;
options.OnTimeout = resultAction;
options.OnError = resultAction;
options.DeadlineTime = deadlineTime;
options.DeadlineAction = deadlineAction;
options.ShowCountdown = true;
// Assert // Assert
Assert.AreEqual("Test Title", options.Title); Assert.AreEqual("Test Title", options.Title);
@@ -62,91 +76,24 @@ namespace WindowsNotifications.Tests
Assert.AreEqual("hero.png", options.HeroImagePath); Assert.AreEqual("hero.png", options.HeroImagePath);
Assert.AreEqual("Test Attribution", options.Attribution); Assert.AreEqual("Test Attribution", options.Attribution);
Assert.AreEqual(30, options.TimeoutInSeconds); Assert.AreEqual(30, options.TimeoutInSeconds);
Assert.AreEqual(1, options.Buttons.Count);
Assert.AreEqual(button, options.Buttons[0]);
Assert.IsTrue(options.Async); 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-tag", options.Tag);
Assert.AreEqual("test-group", options.Group); Assert.AreEqual("test-group", options.Group);
Assert.IsTrue(options.PersistState); Assert.AreEqual(deferralOptions, options.DeferralOptions);
Assert.IsFalse(options.EnableLogging); Assert.IsTrue(options.ShowReminder);
Assert.AreEqual(15, options.ReminderTimeInMinutes);
Assert.IsFalse(options.PersistState);
Assert.IsTrue(options.EnableLogging);
Assert.AreEqual(logAction, options.LogAction); Assert.AreEqual(logAction, options.LogAction);
} Assert.AreEqual(resultAction, options.OnActivated);
Assert.AreEqual(resultAction, options.OnTimeout);
[Test] Assert.AreEqual(resultAction, options.OnError);
public void Buttons_CanBeAdded() Assert.AreEqual(deadlineTime, options.DeadlineTime);
{ Assert.AreEqual(deadlineAction, options.DeadlineAction);
// Arrange Assert.IsTrue(options.ShowCountdown);
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);
} }
} }
} }
@@ -8,82 +8,84 @@ namespace WindowsNotifications.Tests
public class NotificationResultTests public class NotificationResultTests
{ {
[Test] [Test]
public void Constructor_SetsNotificationId() public void NotificationResult_DefaultValues_AreCorrect()
{ {
// Arrange // Arrange & Act
string notificationId = "test-id"; var result = new NotificationResult();
// 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);
// Assert // Assert
Assert.IsNull(result.NotificationId);
Assert.IsFalse(result.Displayed); Assert.IsFalse(result.Displayed);
Assert.IsFalse(result.Activated); Assert.IsFalse(result.Activated);
Assert.IsFalse(result.Dismissed); Assert.IsFalse(result.Dismissed);
Assert.IsNull(result.ClickedButtonId); Assert.IsNull(result.ClickedButtonId);
Assert.IsNull(result.ClickedButtonText); Assert.IsNull(result.ClickedButtonText);
Assert.IsNull(result.ClickedButtonArgument); Assert.IsNull(result.ClickedButtonArgument);
Assert.IsNull(result.ErrorMessage); Assert.IsTrue((DateTime.Now - result.CreatedTime).TotalSeconds < 1);
Assert.IsNull(result.InteractionTime); Assert.IsNull(result.InteractionTime);
// CreatedTime should be close to now Assert.IsNull(result.ErrorMessage);
Assert.Less((DateTime.Now - result.CreatedTime).TotalSeconds, 5); 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] [Test]
public void Error_CreatesErrorResult() public void NotificationResult_SetProperties_ValuesAreCorrect()
{ {
// Arrange // Arrange
string notificationId = "test-id"; var result = new NotificationResult();
string errorMessage = "Test error message"; var createdTime = DateTime.Now.AddMinutes(-5);
var interactionTime = DateTime.Now.AddMinutes(-2);
// Act var deferredUntil = DateTime.Now.AddHours(1);
var result = NotificationResult.Error(notificationId, errorMessage); var deadlineReachedTime = DateTime.Now.AddMinutes(-1);
// 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;
// Act // Act
result.NotificationId = "test-id";
result.Displayed = true; result.Displayed = true;
result.Activated = true; result.Activated = true;
result.Dismissed = true; result.Dismissed = true;
result.ClickedButtonId = "button-id"; result.ClickedButtonId = "button1";
result.ClickedButtonText = "Button Text"; result.ClickedButtonText = "OK";
result.ClickedButtonArgument = "button-arg"; result.ClickedButtonArgument = "ok";
result.ErrorMessage = "Error message"; result.CreatedTime = createdTime;
result.InteractionTime = interactionTime; 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
Assert.AreEqual("test-id", result.NotificationId);
Assert.IsTrue(result.Displayed); Assert.IsTrue(result.Displayed);
Assert.IsTrue(result.Activated); Assert.IsTrue(result.Activated);
Assert.IsTrue(result.Dismissed); Assert.IsTrue(result.Dismissed);
Assert.AreEqual("button-id", result.ClickedButtonId); Assert.AreEqual("button1", result.ClickedButtonId);
Assert.AreEqual("Button Text", result.ClickedButtonText); Assert.AreEqual("OK", result.ClickedButtonText);
Assert.AreEqual("button-arg", result.ClickedButtonArgument); Assert.AreEqual("ok", result.ClickedButtonArgument);
Assert.AreEqual("Error message", result.ErrorMessage); Assert.AreEqual(createdTime, result.CreatedTime);
Assert.AreEqual(interactionTime, result.InteractionTime); 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);
} }
} }
} }
@@ -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")]
+18
View File
@@ -0,0 +1,18 @@
using NUnit.Framework;
namespace WindowsNotifications.Tests
{
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}
}
@@ -1,18 +1,73 @@
<Project Sdk="Microsoft.NET.Sdk"> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\NUnit.3.12.0\build\NUnit.props" Condition="Exists('..\packages\NUnit.3.12.0\build\NUnit.props')" />
<Import Project="..\packages\NUnit3TestAdapter.3.16.1\build\net35\NUnit3TestAdapter.props" Condition="Exists('..\packages\NUnit3TestAdapter.3.16.1\build\net35\NUnit3TestAdapter.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<IsPackable>false</IsPackable> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B1C2D3E4-F5G6-47A8-B9C0-D1E2F3A4B5C6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WindowsNotifications.Tests</RootNamespace>
<AssemblyName>WindowsNotifications.Tests</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NUnit" Version="3.13.3" /> <Reference Include="nunit.framework, Version=3.12.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" /> <HintPath>..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll</HintPath>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\WindowsNotifications\SimpleWindowsNotifications.csproj" /> <Compile Include="NotificationManagerTests.cs" />
<Compile Include="NotificationOptionsTests.cs" />
<Compile Include="NotificationResultTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WindowsNotifications\WindowsNotifications.csproj">
<Project>{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}</Project>
<Name>WindowsNotifications</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\NUnit.3.12.0\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NUnit.3.12.0\build\NUnit.props'))" />
<Error Condition="!Exists('..\packages\NUnit3TestAdapter.3.16.1\build\net35\NUnit3TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NUnit3TestAdapter.3.16.1\build\net35\NUnit3TestAdapter.props'))" />
</Target>
</Project> </Project>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="3.12.0" targetFramework="net472" />
<package id="NUnit3TestAdapter" version="3.16.1" targetFramework="net472" developmentDependency="true" />
</packages>
+10 -6
View File
@@ -1,24 +1,28 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59 VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsNotifications", "WindowsNotifications\WindowsNotifications.csproj", "{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsNotifications", "WindowsNotifications\WindowsNotifications.csproj", "{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsNotifications.Tests", "WindowsNotifications.Tests\WindowsNotifications.Tests.csproj", "{FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {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}.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.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.Build.0 = Release|Any CPU {A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
GlobalSection(SolutionProperties) = preSolution {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Debug|Any CPU.Build.0 = Debug|Any CPU
HideSolutionNode = FALSE {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection {FAD1BEAF-FEEE-43B0-9DFA-BFC55779AE88}.Release|Any CPU.Build.0 = Release|Any CPU
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F1E2D3C4-B5A6-47C8-B9D0-E1F2A3B4C5D6}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Clears all notification results.</para>
/// <para type="description">Clears all notification results from the database.</para>
/// </summary>
[Cmdlet(VerbsCommon.Clear, "AllNotificationResults")]
[OutputType(typeof(bool))]
public class ClearAllNotificationResultsCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Gets all notification results.</para>
/// <para type="description">Gets all notification results from the database.</para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "AllNotificationResults")]
[OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
public class GetAllNotificationResultsCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Gets interactive user sessions.</para>
/// <para type="description">Gets all interactive user sessions.</para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "InteractiveUserSessions")]
[OutputType(typeof(string))]
public class GetInteractiveUserSessionsCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,48 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Gets a notification result.</para>
/// <para type="description">Gets the result of a notification by its ID.</para>
/// </summary>
[Cmdlet(VerbsCommon.Get, "NotificationResult")]
[OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
public class GetNotificationResultCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The ID of the notification.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
public string NotificationId { get; set; }
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,54 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Initializes the Windows Notifications system.</para>
/// <para type="description">Initializes the Windows Notifications system with the specified database path.</para>
/// </summary>
[Cmdlet(VerbsData.Initialize, "WindowsNotifications")]
[OutputType(typeof(bool))]
public class InitializeWindowsNotificationsCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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);
}
}
}
}
@@ -0,0 +1,48 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Removes a notification result.</para>
/// <para type="description">Removes a notification result from the database.</para>
/// </summary>
[Cmdlet(VerbsCommon.Remove, "NotificationResult")]
[OutputType(typeof(bool))]
public class RemoveNotificationResultCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The ID of the notification.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
public string NotificationId { get; set; }
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,123 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Shows a notification.</para>
/// <para type="description">Shows a notification with the specified options.</para>
/// </summary>
[Cmdlet(VerbsCommon.Show, "Notification")]
[OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
public class ShowNotificationCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The title of the notification.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
public string Title { get; set; }
/// <summary>
/// <para type="description">The message body of the notification.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 1)]
public string Message { get; set; }
/// <summary>
/// <para type="description">The buttons to display on the notification.</para>
/// </summary>
[Parameter(Mandatory = false)]
public string[] Buttons { get; set; }
/// <summary>
/// <para type="description">Whether to run the notification asynchronously.</para>
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter Async { get; set; }
/// <summary>
/// <para type="description">The timeout in seconds (0 = no timeout).</para>
/// </summary>
[Parameter(Mandatory = false)]
public int TimeoutInSeconds { get; set; }
/// <summary>
/// <para type="description">The path to the logo image.</para>
/// </summary>
[Parameter(Mandatory = false)]
public string LogoImagePath { get; set; }
/// <summary>
/// <para type="description">The path to the hero image.</para>
/// </summary>
[Parameter(Mandatory = false)]
public string HeroImagePath { get; set; }
/// <summary>
/// <para type="description">The attribution text.</para>
/// </summary>
[Parameter(Mandatory = false)]
public string Attribution { get; set; }
/// <summary>
/// <para type="description">The deadline time for the notification.</para>
/// </summary>
[Parameter(Mandatory = false)]
public DateTime? DeadlineTime { get; set; }
/// <summary>
/// <para type="description">Whether to show a countdown timer on the notification.</para>
/// </summary>
[Parameter(Mandatory = false)]
public SwitchParameter ShowCountdown { get; set; }
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Tests if running as SYSTEM.</para>
/// <para type="description">Tests if the current process is running as SYSTEM.</para>
/// </summary>
[Cmdlet(VerbsDiagnostic.Test, "SystemContext")]
[OutputType(typeof(bool))]
public class TestSystemContextCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Management.Automation;
namespace WindowsNotifications.Cmdlets
{
/// <summary>
/// <para type="synopsis">Waits for a notification to complete.</para>
/// <para type="description">Waits for a notification to complete and returns the result.</para>
/// </summary>
[Cmdlet(VerbsLifecycle.Wait, "Notification")]
[OutputType(typeof(WindowsNotifications.Models.NotificationResult))]
public class WaitNotificationCmdlet : PSCmdlet
{
private WindowsNotifications.NotificationManager _notificationManager;
/// <summary>
/// <para type="description">The ID of the notification.</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
public string NotificationId { get; set; }
/// <summary>
/// <para type="description">The timeout in seconds (-1 = wait indefinitely).</para>
/// </summary>
[Parameter(Mandatory = false)]
public int TimeoutInSeconds { get; set; }
/// <summary>
/// <para type="description">The path to the database file.</para>
/// </summary>
[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));
}
}
}
}
+29 -103
View File
@@ -1,79 +1,51 @@
using System; using System;
using System.Diagnostics;
namespace WindowsNotifications.Models namespace WindowsNotifications.Models
{ {
/// <summary> /// <summary>
/// Represents an action to take when a notification deadline is reached /// Represents an action to take when a notification deadline is reached.
/// </summary> /// </summary>
public class DeadlineAction public class DeadlineAction
{ {
/// <summary> /// <summary>
/// The type of action to take /// Gets or sets the type of deadline action.
/// </summary> /// </summary>
public DeadlineActionType ActionType { get; set; } = DeadlineActionType.None; public DeadlineActionType ActionType { get; set; }
/// <summary> /// <summary>
/// The command to execute (for Process action type) /// Gets or sets the command to execute when the deadline is reached.
/// </summary> /// </summary>
public string Command { get; set; } public string Command { get; set; }
/// <summary> /// <summary>
/// The arguments for the command (for Process action type) /// Gets or sets the script to execute when the deadline is reached.
/// </summary>
public string Arguments { get; set; }
/// <summary>
/// The URL to open (for Url action type)
/// </summary>
public string Url { get; set; }
/// <summary>
/// The script to execute (for Script action type)
/// </summary> /// </summary>
public string Script { get; set; } public string Script { get; set; }
/// <summary> /// <summary>
/// The custom action to execute (for Custom action type) /// Gets or sets the action to execute when the deadline is reached.
/// </summary> /// </summary>
public Action<NotificationResult> CustomAction { get; set; } public Action Action { get; set; }
/// <summary> /// <summary>
/// Creates a new DeadlineAction with no action /// Creates a new deadline action that executes a command.
/// </summary> /// </summary>
public DeadlineAction() /// <param name="command">The command to execute.</param>
{ /// <returns>A new deadline action.</returns>
} public static DeadlineAction ExecuteCommand(string command)
/// <summary>
/// Creates a new DeadlineAction that runs a process
/// </summary>
/// <param name="command">The command to execute</param>
/// <param name="arguments">The arguments for the command</param>
public DeadlineAction(string command, string arguments = null)
{
ActionType = DeadlineActionType.Process;
Command = command;
Arguments = arguments;
}
/// <summary>
/// Creates a new DeadlineAction that opens a URL
/// </summary>
/// <param name="url">The URL to open</param>
public static DeadlineAction OpenUrl(string url)
{ {
return new DeadlineAction return new DeadlineAction
{ {
ActionType = DeadlineActionType.Url, ActionType = DeadlineActionType.Command,
Url = url Command = command
}; };
} }
/// <summary> /// <summary>
/// Creates a new DeadlineAction that executes a PowerShell script /// Creates a new deadline action that executes a PowerShell script.
/// </summary> /// </summary>
/// <param name="script">The PowerShell script to execute</param> /// <param name="script">The PowerShell script to execute.</param>
/// <returns>A new deadline action.</returns>
public static DeadlineAction ExecuteScript(string script) public static DeadlineAction ExecuteScript(string script)
{ {
return new DeadlineAction return new DeadlineAction
@@ -84,89 +56,43 @@ namespace WindowsNotifications.Models
} }
/// <summary> /// <summary>
/// Creates a new DeadlineAction that executes a custom action /// Creates a new deadline action that executes a .NET action.
/// </summary> /// </summary>
/// <param name="action">The custom action to execute</param> /// <param name="action">The action to execute.</param>
public static DeadlineAction ExecuteCustomAction(Action<NotificationResult> action) /// <returns>A new deadline action.</returns>
public static DeadlineAction ExecuteAction(Action action)
{ {
return new DeadlineAction return new DeadlineAction
{ {
ActionType = DeadlineActionType.Custom, ActionType = DeadlineActionType.Action,
CustomAction = action Action = action
}; };
} }
/// <summary>
/// Executes the deadline action
/// </summary>
/// <param name="result">The notification result</param>
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}");
}
}
} }
/// <summary> /// <summary>
/// The type of action to take when a deadline is reached /// Defines the types of deadline actions.
/// </summary> /// </summary>
public enum DeadlineActionType public enum DeadlineActionType
{ {
/// <summary> /// <summary>
/// No action /// No action.
/// </summary> /// </summary>
None, None,
/// <summary> /// <summary>
/// Run a process /// Execute a command.
/// </summary> /// </summary>
Process, Command,
/// <summary> /// <summary>
/// Open a URL /// Execute a PowerShell script.
/// </summary>
Url,
/// <summary>
/// Execute a PowerShell script
/// </summary> /// </summary>
Script, Script,
/// <summary> /// <summary>
/// Execute a custom action /// Execute a .NET action.
/// </summary> /// </summary>
Custom Action
} }
} }
+23 -72
View File
@@ -4,112 +4,63 @@ using System.Collections.Generic;
namespace WindowsNotifications.Models namespace WindowsNotifications.Models
{ {
/// <summary> /// <summary>
/// Options for configuring notification deferrals /// Options for configuring notification deferrals.
/// </summary> /// </summary>
public class DeferralOptions public class DeferralOptions
{ {
/// <summary> /// <summary>
/// Whether deferrals are enabled for this notification /// Gets or sets whether deferrals are enabled.
/// </summary> /// </summary>
public bool Enabled { get; set; } = true; public bool Enabled { get; set; } = true;
/// <summary> /// <summary>
/// The maximum number of times the notification can be deferred /// Gets or sets the maximum number of deferrals allowed.
/// </summary> /// </summary>
public int MaxDeferrals { get; set; } = 3; public int MaxDeferrals { get; set; } = 3;
/// <summary> /// <summary>
/// The available deferral options to show to the user /// Gets or sets the default deferral time in minutes.
/// </summary> /// </summary>
public List<DeferralOption> DeferralChoices { get; set; } = new List<DeferralOption>(); public int DefaultDeferralTimeInMinutes { get; set; } = 60;
/// <summary> /// <summary>
/// The text to display for the deferral button /// Gets or sets the list of predefined deferral options in minutes.
/// </summary>
public List<int> DeferralOptions { get; set; } = new List<int> { 15, 30, 60, 240, 480 };
/// <summary>
/// Gets or sets the text for the deferral button.
/// </summary> /// </summary>
public string DeferButtonText { get; set; } = "Defer"; public string DeferButtonText { get; set; } = "Defer";
/// <summary> /// <summary>
/// The text to display for the deferral selection prompt /// Gets or sets the text for the deferral dropdown.
/// </summary> /// </summary>
public string DeferralPrompt { get; set; } = "Defer until:"; public string DeferDropdownText { get; set; } = "Defer for:";
/// <summary> /// <summary>
/// Whether to schedule a reminder when the notification is deferred /// Gets or sets the format for the deferral option text.
/// </summary> /// </summary>
public bool ScheduleReminder { get; set; } = true; public string DeferralOptionFormat { get; set; } = "{0} minutes";
/// <summary> /// <summary>
/// Whether to enforce the maximum number of deferrals /// Gets or sets the format for the deferral option text when the time is in hours.
/// </summary> /// </summary>
public bool EnforceMaxDeferrals { get; set; } = true; public string DeferralOptionHourFormat { get; set; } = "{0} hours";
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public DeadlineAction MaxDeferralsAction { get; set; } public DateTime? AbsoluteDeadline { get; set; }
/// <summary> /// <summary>
/// The current number of times this notification has been deferred /// Gets or sets the text to display when the absolute deadline is approaching.
/// </summary> /// </summary>
public int CurrentDeferralCount { get; set; } = 0; public string DeadlineApproachingText { get; set; } = "Final deadline approaching. No further deferrals will be allowed.";
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public DeferralOptions() public int DeadlineApproachingWarningMinutes { get; set; } = 120;
{
// 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();
}
/// <summary>
/// Creates a new DeferralOptions instance with custom deferral choices
/// </summary>
/// <param name="deferralChoices">The deferral choices to offer</param>
public DeferralOptions(List<DeferralOption> deferralChoices)
{
DeferralChoices = deferralChoices;
}
}
/// <summary>
/// Represents a single deferral option
/// </summary>
public class DeferralOption
{
/// <summary>
/// The text to display for this deferral option
/// </summary>
public string Text { get; set; }
/// <summary>
/// The time span to defer for
/// </summary>
public TimeSpan DeferralTime { get; set; }
/// <summary>
/// Optional identifier for this deferral option
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Creates a new deferral option
/// </summary>
/// <param name="text">The text to display</param>
/// <param name="deferralTime">The time span to defer for</param>
/// <param name="id">Optional identifier</param>
public DeferralOption(string text, TimeSpan deferralTime, string id = null)
{
Text = text;
DeferralTime = deferralTime;
if (!string.IsNullOrEmpty(id))
Id = id;
}
} }
} }
@@ -0,0 +1,64 @@
namespace WindowsNotifications.Models
{
/// <summary>
/// Represents a button in a notification.
/// </summary>
public class NotificationButton
{
/// <summary>
/// Initializes a new instance of the <see cref="NotificationButton"/> class.
/// </summary>
public NotificationButton()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NotificationButton"/> class with the specified text and ID.
/// </summary>
/// <param name="text">The text to display on the button.</param>
/// <param name="id">The unique identifier for the button.</param>
public NotificationButton(string text, string id)
{
Text = text;
Id = id;
}
/// <summary>
/// Initializes a new instance of the <see cref="NotificationButton"/> class with the specified text, ID, and argument.
/// </summary>
/// <param name="text">The text to display on the button.</param>
/// <param name="id">The unique identifier for the button.</param>
/// <param name="argument">The argument to return when the button is clicked.</param>
public NotificationButton(string text, string id, string argument)
{
Text = text;
Id = id;
Argument = argument;
}
/// <summary>
/// Gets or sets the text to display on the button.
/// </summary>
public string Text { get; set; }
/// <summary>
/// Gets or sets the unique identifier for the button.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Gets or sets the argument to return when the button is clicked.
/// </summary>
public string Argument { get; set; }
/// <summary>
/// Gets or sets the background color of the button (hex format: #RRGGBB).
/// </summary>
public string BackgroundColor { get; set; }
/// <summary>
/// Gets or sets the foreground (text) color of the button (hex format: #RRGGBB).
/// </summary>
public string ForegroundColor { get; set; }
}
}
@@ -4,286 +4,123 @@ using System.Collections.Generic;
namespace WindowsNotifications.Models namespace WindowsNotifications.Models
{ {
/// <summary> /// <summary>
/// Options for configuring a notification /// Options for configuring a notification.
/// </summary> /// </summary>
public class NotificationOptions public class NotificationOptions
{ {
/// <summary> /// <summary>
/// The title of the notification /// Gets or sets the title of the notification.
/// </summary> /// </summary>
public string Title { get; set; } public string Title { get; set; }
/// <summary> /// <summary>
/// The main message body of the notification /// Gets or sets the main message body of the notification.
/// </summary> /// </summary>
public string Message { get; set; } public string Message { get; set; }
/// <summary> /// <summary>
/// Optional logo image path or URL /// Gets or sets the optional logo image path.
/// </summary> /// </summary>
public string LogoImagePath { get; set; } public string LogoImagePath { get; set; }
/// <summary> /// <summary>
/// Optional hero image path or URL /// Gets or sets the optional hero image path.
/// </summary> /// </summary>
public string HeroImagePath { get; set; } public string HeroImagePath { get; set; }
/// <summary> /// <summary>
/// Optional inline image path or URL /// Gets or sets the optional attribution text.
/// </summary>
public string InlineImagePath { get; set; }
/// <summary>
/// Optional app icon path or URL
/// </summary>
public string AppIconPath { get; set; }
/// <summary>
/// Optional badge logo path or URL
/// </summary>
public string BadgeLogoPath { get; set; }
/// <summary>
/// Optional background image path or URL for the toast
/// </summary>
public string BackgroundImagePath { get; set; }
/// <summary>
/// Optional attribution text
/// </summary> /// </summary>
public string Attribution { get; set; } public string Attribution { get; set; }
/// <summary> /// <summary>
/// Optional timeout in seconds. If set to 0, the notification will not timeout. /// Gets or sets the optional timeout in seconds (0 = no timeout).
/// </summary> /// </summary>
public int TimeoutInSeconds { get; set; } = 0; public int TimeoutInSeconds { get; set; }
/// <summary> /// <summary>
/// Optional list of buttons to display on the notification /// Gets or sets the optional list of buttons to display.
/// </summary> /// </summary>
public List<NotificationButton> Buttons { get; set; } = new List<NotificationButton>(); public List<NotificationButton> Buttons { get; set; } = new List<NotificationButton>();
/// <summary> /// <summary>
/// Whether to run the notification asynchronously /// Gets or sets whether to run the notification asynchronously.
/// </summary> /// </summary>
public bool Async { get; set; } = false; public bool Async { get; set; }
/// <summary> /// <summary>
/// Optional scenario for the notification (alarm, reminder, incomingCall, urgent) /// Gets or sets the optional unique identifier for the notification.
/// </summary>
public string Scenario { get; set; }
/// <summary>
/// Optional launch argument for the notification
/// </summary>
public string LaunchArgument { get; set; }
/// <summary>
/// Optional audio source for the notification
/// </summary>
public string AudioSource { get; set; }
/// <summary>
/// Whether to loop the audio
/// </summary>
public bool LoopAudio { get; set; } = false;
/// <summary>
/// Optional custom branding text (e.g., company or department name)
/// </summary>
public string BrandingText { get; set; }
/// <summary>
/// Optional custom branding color (hex format: #RRGGBB)
/// </summary>
public string BrandingColor { get; set; }
/// <summary>
/// Optional custom accent color for the notification (hex format: #RRGGBB)
/// </summary>
public string AccentColor { get; set; }
/// <summary>
/// Optional custom font family for the notification text
/// </summary>
public string FontFamily { get; set; }
/// <summary>
/// Whether to use dark theme for the notification
/// </summary>
public bool UseDarkTheme { get; set; } = false;
/// <summary>
/// Whether to show the notification in silent mode (no sound)
/// </summary>
public bool SilentMode { get; set; } = false;
/// <summary>
/// Whether to show a progress bar on the notification
/// </summary>
public bool ShowProgressBar { get; set; } = false;
/// <summary>
/// The progress value (0-100) for the progress bar
/// </summary>
public int ProgressValue { get; set; } = 0;
/// <summary>
/// The status text for the progress bar
/// </summary>
public string ProgressStatus { get; set; }
/// <summary>
/// Whether to show a countdown timer on the notification
/// </summary>
public bool ShowCountdown { get; set; } = false;
/// <summary>
/// The deadline time for the notification
/// </summary>
public DateTime? DeadlineTime { get; set; }
/// <summary>
/// The action to take when the deadline is reached
/// </summary>
public DeadlineAction DeadlineAction { get; set; }
/// <summary>
/// Optional unique identifier for the notification
/// </summary> /// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString(); public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary> /// <summary>
/// Optional tag for grouping notifications /// Gets or sets the optional tag for grouping notifications.
/// </summary> /// </summary>
public string Tag { get; set; } public string Tag { get; set; }
/// <summary> /// <summary>
/// Optional group name for grouping notifications /// Gets or sets the optional group name for grouping notifications.
/// </summary> /// </summary>
public string Group { get; set; } public string Group { get; set; }
/// <summary> /// <summary>
/// Optional deferral options for notifications that can be deferred /// Gets or sets the optional deferral options.
/// </summary> /// </summary>
public DeferralOptions DeferralOptions { get; set; } public DeferralOptions DeferralOptions { get; set; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public bool ShowReminder { get; set; } = false; public bool ShowReminder { get; set; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public int ReminderTimeInMinutes { get; set; } = 60; public int ReminderTimeInMinutes { get; set; } = 60;
/// <summary> /// <summary>
/// Whether to persist the notification state in the database /// Gets or sets whether to persist the notification state in the database.
/// </summary> /// </summary>
public bool PersistState { get; set; } = false; public bool PersistState { get; set; } = true;
/// <summary> /// <summary>
/// Whether to enable logging for this notification /// Gets or sets whether to enable logging for this notification.
/// </summary> /// </summary>
public bool EnableLogging { get; set; } = true; public bool EnableLogging { get; set; }
/// <summary> /// <summary>
/// Optional action to handle logging (if null, logs to Debug output) /// Gets or sets the optional action to handle logging.
/// </summary> /// </summary>
public Action<string> LogAction { get; set; } public Action<string> LogAction { get; set; }
/// <summary> /// <summary>
/// Optional action to execute when the notification is activated /// Gets or sets the optional action to execute when the notification is activated.
/// </summary> /// </summary>
public Action<NotificationResult> OnActivated { get; set; } public Action<NotificationResult> OnActivated { get; set; }
/// <summary> /// <summary>
/// Optional action to execute when the notification times out /// Gets or sets the optional action to execute when the notification times out.
/// </summary> /// </summary>
public Action<NotificationResult> OnTimeout { get; set; } public Action<NotificationResult> OnTimeout { get; set; }
/// <summary> /// <summary>
/// Optional action to execute when an error occurs /// Gets or sets the optional action to execute when an error occurs.
/// </summary> /// </summary>
public Action<NotificationResult> OnError { get; set; } public Action<NotificationResult> OnError { get; set; }
/// <summary> /// <summary>
/// Optional serialized data for reminders /// Gets or sets the optional deadline time for the notification.
/// </summary> /// </summary>
public string ReminderData { get; set; } public DateTime? DeadlineTime { get; set; }
}
/// <summary>
/// Represents a button on a notification
/// </summary>
public class NotificationButton
{
/// <summary>
/// The text to display on the button
/// </summary>
public string Text { get; set; }
/// <summary> /// <summary>
/// Optional identifier for the button /// Gets or sets the action to take when the deadline is reached.
/// </summary> /// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString(); public DeadlineAction DeadlineAction { get; set; }
/// <summary> /// <summary>
/// Optional argument to pass when the button is clicked /// Gets or sets whether to show a countdown timer on the notification.
/// </summary> /// </summary>
public string Argument { get; set; } public bool ShowCountdown { get; set; }
/// <summary>
/// Optional image URI for the button (file path or URL)
/// </summary>
public string ImageUri { get; set; }
/// <summary>
/// Whether this button should appear in the context menu
/// </summary>
public bool IsContextMenu { get; set; } = false;
/// <summary>
/// Optional tooltip text for the button
/// </summary>
public string ToolTip { get; set; }
/// <summary>
/// Optional background color for the button (hex format: #RRGGBB)
/// </summary>
public string BackgroundColor { get; set; }
/// <summary>
/// Optional text color for the button (hex format: #RRGGBB)
/// </summary>
public string TextColor { get; set; }
/// <summary>
/// Creates a new notification button with the specified text
/// </summary>
/// <param name="text">The text to display on the button</param>
/// <param name="id">Optional identifier for the button</param>
/// <param name="argument">Optional argument to pass when the button is clicked</param>
public NotificationButton(string text, string id = null, string argument = null)
{
Text = text;
if (!string.IsNullOrEmpty(id))
Id = id;
Argument = argument ?? Id;
}
/// <summary>
/// Creates a new notification button with the specified text and image
/// </summary>
/// <param name="text">The text to display on the button</param>
/// <param name="imageUri">The image URI for the button (file path or URL)</param>
/// <param name="id">Optional identifier for the button</param>
/// <param name="argument">Optional argument to pass when the button is clicked</param>
public NotificationButton(string text, string imageUri, string id = null, string argument = null)
: this(text, id, argument)
{
ImageUri = imageUri;
}
} }
} }
@@ -3,127 +3,103 @@ using System;
namespace WindowsNotifications.Models namespace WindowsNotifications.Models
{ {
/// <summary> /// <summary>
/// Represents the result of a notification interaction /// Represents the result of a notification interaction.
/// </summary> /// </summary>
public class NotificationResult public class NotificationResult
{ {
/// <summary> /// <summary>
/// The unique identifier of the notification /// Gets or sets the unique identifier of the notification.
/// </summary> /// </summary>
public string NotificationId { get; set; } public string NotificationId { get; set; }
/// <summary> /// <summary>
/// Whether the notification was successfully displayed /// Gets or sets whether the notification was successfully displayed.
/// </summary> /// </summary>
public bool Displayed { get; set; } public bool Displayed { get; set; }
/// <summary> /// <summary>
/// Whether the notification was activated (clicked) /// Gets or sets whether the notification was activated (clicked).
/// </summary> /// </summary>
public bool Activated { get; set; } public bool Activated { get; set; }
/// <summary> /// <summary>
/// Whether the notification was dismissed /// Gets or sets whether the notification was dismissed.
/// </summary> /// </summary>
public bool Dismissed { get; set; } public bool Dismissed { get; set; }
/// <summary> /// <summary>
/// The ID of the button that was clicked, if any /// Gets or sets the ID of the button that was clicked, if any.
/// </summary> /// </summary>
public string ClickedButtonId { get; set; } public string ClickedButtonId { get; set; }
/// <summary> /// <summary>
/// The text of the button that was clicked, if any /// Gets or sets the text of the button that was clicked, if any.
/// </summary> /// </summary>
public string ClickedButtonText { get; set; } public string ClickedButtonText { get; set; }
/// <summary> /// <summary>
/// The argument of the button that was clicked, if any /// Gets or sets the argument of the button that was clicked, if any.
/// </summary> /// </summary>
public string ClickedButtonArgument { get; set; } public string ClickedButtonArgument { get; set; }
/// <summary> /// <summary>
/// The time when the notification was created /// Gets or sets the time when the notification was created.
/// </summary> /// </summary>
public DateTime CreatedTime { get; set; } = DateTime.Now; public DateTime CreatedTime { get; set; } = DateTime.Now;
/// <summary> /// <summary>
/// The time when the notification was interacted with, if any /// Gets or sets the time when the notification was interacted with, if any.
/// </summary> /// </summary>
public DateTime? InteractionTime { get; set; } public DateTime? InteractionTime { get; set; }
/// <summary> /// <summary>
/// Any error message that occurred during the notification process /// Gets or sets any error message that occurred during the notification process.
/// </summary> /// </summary>
public string ErrorMessage { get; set; } public string ErrorMessage { get; set; }
/// <summary> /// <summary>
/// The error code, if an error occurred /// Gets or sets the error code, if an error occurred.
/// </summary> /// </summary>
public string ErrorCode { get; set; } public string ErrorCode { get; set; }
/// <summary> /// <summary>
/// Whether the notification was deferred /// Gets or sets whether the notification was deferred.
/// </summary> /// </summary>
public bool Deferred { get; set; } public bool Deferred { get; set; }
/// <summary> /// <summary>
/// The time when the notification was deferred until, if applicable /// Gets or sets the time when the notification was deferred until, if applicable.
/// </summary> /// </summary>
public DateTime? DeferredUntil { get; set; } public DateTime? DeferredUntil { get; set; }
/// <summary> /// <summary>
/// The reason for deferral, if applicable /// Gets or sets the reason for deferral, if applicable.
/// </summary> /// </summary>
public string DeferralReason { get; set; } public string DeferralReason { get; set; }
/// <summary> /// <summary>
/// The reason for dismissal, if applicable /// Gets or sets the reason for dismissal, if applicable.
/// </summary> /// </summary>
public string DismissalReason { get; set; } public string DismissalReason { get; set; }
/// <summary> /// <summary>
/// The system action that was taken (e.g., snooze, dismiss) /// Gets or sets the system action that was taken (e.g., snooze, dismiss).
/// </summary> /// </summary>
public string SystemAction { get; set; } public string SystemAction { get; set; }
/// <summary> /// <summary>
/// Whether the deadline was reached /// Gets or sets whether the deadline was reached.
/// </summary> /// </summary>
public bool DeadlineReached { get; set; } public bool DeadlineReached { get; set; }
/// <summary> /// <summary>
/// The time when the deadline was reached, if applicable /// Gets or sets the time when the deadline was reached, if applicable.
/// </summary> /// </summary>
public DateTime? DeadlineReachedTime { get; set; } public DateTime? DeadlineReachedTime { get; set; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public string DeadlineAction { get; set; } public string DeadlineAction { get; set; }
/// <summary>
/// Creates a new notification result with the specified notification ID
/// </summary>
/// <param name="notificationId">The unique identifier of the notification</param>
public NotificationResult(string notificationId)
{
NotificationId = notificationId;
}
/// <summary>
/// Creates a new error notification result
/// </summary>
/// <param name="notificationId">The unique identifier of the notification</param>
/// <param name="errorMessage">The error message</param>
/// <returns>A notification result with the error message</returns>
public static NotificationResult Error(string notificationId, string errorMessage)
{
return new NotificationResult(notificationId)
{
ErrorMessage = errorMessage,
Displayed = false
};
}
} }
} }
@@ -1,116 +0,0 @@
using System;
using System.Collections.Generic;
namespace WindowsNotifications.Models
{
/// <summary>
/// Options for configuring a notification
/// </summary>
public class NotificationOptions
{
/// <summary>
/// The title of the notification
/// </summary>
public string Title { get; set; }
/// <summary>
/// The main message body of the notification
/// </summary>
public string Message { get; set; }
/// <summary>
/// Optional logo image path
/// </summary>
public string LogoImagePath { get; set; }
/// <summary>
/// Optional hero image path
/// </summary>
public string HeroImagePath { get; set; }
/// <summary>
/// Optional attribution text
/// </summary>
public string Attribution { get; set; }
/// <summary>
/// Optional timeout in seconds. If set to 0, the notification will not timeout.
/// </summary>
public int TimeoutInSeconds { get; set; } = 0;
/// <summary>
/// Optional list of buttons to display on the notification
/// </summary>
public List<NotificationButton> Buttons { get; set; } = new List<NotificationButton>();
/// <summary>
/// Whether to run the notification asynchronously
/// </summary>
public bool Async { get; set; } = false;
/// <summary>
/// Optional unique identifier for the notification
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Optional tag for grouping notifications
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Optional group name for grouping notifications
/// </summary>
public string Group { get; set; }
/// <summary>
/// Whether to persist the notification state in the database
/// </summary>
public bool PersistState { get; set; } = false;
/// <summary>
/// Whether to enable logging for this notification
/// </summary>
public bool EnableLogging { get; set; } = true;
/// <summary>
/// Optional action to handle logging (if null, logs to Debug output)
/// </summary>
public Action<string> LogAction { get; set; }
}
/// <summary>
/// Represents a button on a notification
/// </summary>
public class NotificationButton
{
/// <summary>
/// The text to display on the button
/// </summary>
public string Text { get; set; }
/// <summary>
/// Optional identifier for the button
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// Optional argument to pass when the button is clicked
/// </summary>
public string Argument { get; set; }
/// <summary>
/// Creates a new notification button with the specified text
/// </summary>
/// <param name="text">The text to display on the button</param>
/// <param name="id">Optional identifier for the button</param>
/// <param name="argument">Optional argument to pass when the button is clicked</param>
public NotificationButton(string text, string id = null, string argument = null)
{
Text = text;
if (!string.IsNullOrEmpty(id))
Id = id;
Argument = argument ?? Id;
}
}
}
@@ -1,84 +0,0 @@
using System;
namespace WindowsNotifications.Models
{
/// <summary>
/// Represents the result of a notification interaction
/// </summary>
public class NotificationResult
{
/// <summary>
/// The unique identifier of the notification
/// </summary>
public string NotificationId { get; set; }
/// <summary>
/// Whether the notification was successfully displayed
/// </summary>
public bool Displayed { get; set; }
/// <summary>
/// Whether the notification was activated (clicked)
/// </summary>
public bool Activated { get; set; }
/// <summary>
/// Whether the notification was dismissed
/// </summary>
public bool Dismissed { get; set; }
/// <summary>
/// The ID of the button that was clicked, if any
/// </summary>
public string ClickedButtonId { get; set; }
/// <summary>
/// The text of the button that was clicked, if any
/// </summary>
public string ClickedButtonText { get; set; }
/// <summary>
/// The argument of the button that was clicked, if any
/// </summary>
public string ClickedButtonArgument { get; set; }
/// <summary>
/// The time when the notification was created
/// </summary>
public DateTime CreatedTime { get; set; } = DateTime.Now;
/// <summary>
/// The time when the notification was interacted with, if any
/// </summary>
public DateTime? InteractionTime { get; set; }
/// <summary>
/// Any error message that occurred during the notification process
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// Creates a new notification result with the specified notification ID
/// </summary>
/// <param name="notificationId">The unique identifier of the notification</param>
public NotificationResult(string notificationId)
{
NotificationId = notificationId;
}
/// <summary>
/// Creates a new error notification result
/// </summary>
/// <param name="notificationId">The unique identifier of the notification</param>
/// <param name="errorMessage">The error message</param>
/// <returns>A notification result with the error message</returns>
public static NotificationResult Error(string notificationId, string errorMessage)
{
return new NotificationResult(notificationId)
{
ErrorMessage = errorMessage,
Displayed = false
};
}
}
}
+160 -250
View File
@@ -1,114 +1,118 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using WindowsNotifications.Models; using WindowsNotifications.Models;
using WindowsNotifications.Services; using WindowsNotifications.Services;
namespace WindowsNotifications namespace WindowsNotifications
{ {
/// <summary> /// <summary>
/// Main entry point for the Windows Notifications library /// The main entry point for the Windows Notifications library.
/// </summary> /// </summary>
public class NotificationManager public class NotificationManager
{ {
private readonly UserSessionManager _sessionManager; private readonly string _databasePath;
private readonly ToastNotificationService _toastService;
private readonly DatabaseService _databaseService; private readonly DatabaseService _databaseService;
private readonly Dictionary<string, Timer> _reminderTimers = new Dictionary<string, Timer>(); private readonly ToastNotificationService _toastService;
private readonly Dictionary<string, Timer> _deadlineTimers = new Dictionary<string, Timer>(); private readonly UserSessionManager _sessionManager;
private readonly Timer _cleanupTimer;
/// <summary> /// <summary>
/// Gets or sets the path to the LiteDB database file /// Initializes a new instance of the <see cref="NotificationManager"/> class with the default database path.
/// </summary> /// </summary>
public string DatabasePath { get; private set; } public NotificationManager() : this(GetDefaultDatabasePath())
/// <summary>
/// Creates a new NotificationManager with the default database path
/// </summary>
public NotificationManager() : this(null)
{ {
} }
/// <summary> /// <summary>
/// Creates a new NotificationManager with the specified database path /// Initializes a new instance of the <see cref="NotificationManager"/> class with the specified database path.
/// </summary> /// </summary>
/// <param name="databasePath">The path to the LiteDB database file, or null to use the default</param> /// <param name="databasePath">The path to the database file.</param>
public NotificationManager(string databasePath) public NotificationManager(string databasePath)
{ {
DatabasePath = databasePath; _databasePath = databasePath;
_sessionManager = new UserSessionManager(); _databaseService = new DatabaseService(_databasePath);
_toastService = new ToastNotificationService(); _toastService = new ToastNotificationService();
_databaseService = new DatabaseService(databasePath); _sessionManager = new UserSessionManager();
// Initialize cleanup timer to run every hour
_cleanupTimer = new Timer(CleanupExpiredNotifications, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1));
} }
/// <summary> /// <summary>
/// Shows a notification with the specified options /// Shows a notification with the specified options.
/// </summary> /// </summary>
/// <param name="options">The notification options</param> /// <param name="options">The notification options.</param>
/// <returns>The result of the notification</returns> /// <returns>The notification result.</returns>
public NotificationResult ShowNotification(NotificationOptions options) public NotificationResult ShowNotification(NotificationOptions options)
{ {
// Validate options
if (options == null) if (options == null)
{
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(options));
}
if (string.IsNullOrEmpty(options.Title) && string.IsNullOrEmpty(options.Message)) // Generate a unique ID if not provided
throw new ArgumentException("Either Title or Message must be specified"); if (string.IsNullOrEmpty(options.Id))
// 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())
{ {
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)); NotificationId = options.Id,
} Displayed = false,
catch (Exception ex) 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 // Show the notification
if (options.ShowReminder && result.Displayed && !result.Activated && !result.Dismissed) NotificationResult notificationResult;
if (IsRunningAsSystem())
{ {
SetupReminderTimer(options); // Show notification using impersonation
notificationResult = _sessionManager.RunAsInteractiveUser(() => _toastService.ShowNotification(options));
} }
else
// Set up a deadline timer if specified
if (options.DeadlineTime.HasValue && result.Displayed && !result.Activated && !result.Dismissed)
{ {
SetupDeadlineTimer(options, result); // Show notification directly
notificationResult = _toastService.ShowNotification(options);
} }
// Persist the result if requested // Save the result if persistence is enabled
if (options.PersistState && result != null) 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;
} }
/// <summary> /// <summary>
/// Shows a simple notification with the specified title and message /// Shows a simple notification with the specified title and message.
/// </summary> /// </summary>
/// <param name="title">The title of the notification</param> /// <param name="title">The title of the notification.</param>
/// <param name="message">The message body of the notification</param> /// <param name="message">The message body of the notification.</param>
/// <returns>The result of the notification</returns> /// <returns>The notification result.</returns>
public NotificationResult ShowSimpleNotification(string title, string message) public NotificationResult ShowSimpleNotification(string title, string message)
{ {
var options = new NotificationOptions var options = new NotificationOptions
@@ -121,267 +125,173 @@ namespace WindowsNotifications
} }
/// <summary> /// <summary>
/// Shows a notification with buttons /// Shows a notification with buttons.
/// </summary> /// </summary>
/// <param name="title">The title of the notification</param> /// <param name="title">The title of the notification.</param>
/// <param name="message">The message body of the notification</param> /// <param name="message">The message body of the notification.</param>
/// <param name="buttons">The buttons to display</param> /// <param name="buttons">The buttons to display.</param>
/// <returns>The result of the notification</returns> /// <returns>The notification result.</returns>
public NotificationResult ShowNotificationWithButtons(string title, string message, params string[] buttons) public NotificationResult ShowNotificationWithButtons(string title, string message, params string[] buttons)
{ {
var options = new NotificationOptions var options = new NotificationOptions
{ {
Title = title, Title = title,
Message = message, Message = message
Buttons = buttons.Select(b => new NotificationButton(b)).ToList()
}; };
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); return ShowNotification(options);
} }
/// <summary> /// <summary>
/// Shows a reboot notification with deferral options /// Shows a reboot notification with deferral options.
/// </summary> /// </summary>
/// <param name="title">The title of the notification</param> /// <param name="title">The title of the notification.</param>
/// <param name="message">The message body of the notification</param> /// <param name="message">The message body of the notification.</param>
/// <param name="rebootButtonText">The text for the reboot button</param> /// <param name="rebootButtonText">The text for the reboot button.</param>
/// <param name="deferButtonText">The text for the defer button</param> /// <param name="deferButtonText">The text for the defer button.</param>
/// <returns>The result of the notification</returns> /// <returns>The notification result.</returns>
public NotificationResult ShowRebootNotification(string title, string message, string rebootButtonText = "Reboot Now", string deferButtonText = "Defer") public NotificationResult ShowRebootNotification(string title, string message, string rebootButtonText = "Reboot Now", string deferButtonText = "Defer")
{ {
var options = new NotificationOptions var options = new NotificationOptions
{ {
Title = title, Title = title,
Message = message, Message = message,
Buttons = new List<NotificationButton> { new NotificationButton(rebootButtonText, "reboot") }, DeferralOptions = new DeferralOptions
DeferralOptions = new DeferralOptions { DeferButtonText = deferButtonText }, {
PersistState = true Enabled = true,
DeferButtonText = deferButtonText
}
}; };
options.Buttons.Add(new NotificationButton(rebootButtonText, "reboot", "reboot"));
return ShowNotification(options); return ShowNotification(options);
} }
/// <summary> /// <summary>
/// Gets the result of a notification /// Gets the result of a notification.
/// </summary> /// </summary>
/// <param name="notificationId">The ID of the notification</param> /// <param name="notificationId">The unique identifier of the notification.</param>
/// <returns>The notification result, or null if not found</returns> /// <returns>The notification result, or null if not found.</returns>
public NotificationResult GetNotificationResult(string notificationId) public NotificationResult GetNotificationResult(string notificationId)
{ {
// Try to get from the toast service first return _databaseService.GetNotificationResult(notificationId);
var result = _toastService.GetNotificationResult(notificationId); }
// If not found, try to get from the database /// <summary>
if (result == null) /// Waits for a notification to complete.
result = _databaseService.GetNotificationResult(notificationId); /// </summary>
/// <param name="notificationId">The unique identifier of the notification.</param>
/// <param name="timeout">The timeout in milliseconds, or -1 to wait indefinitely.</param>
/// <returns>The notification result.</returns>
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; return result;
} }
/// <summary> /// <summary>
/// Waits for a notification to complete /// Gets all notification results from the database.
/// </summary> /// </summary>
/// <param name="notificationId">The ID of the notification</param> /// <returns>A list of notification results.</returns>
/// <param name="timeout">The timeout in milliseconds, or -1 to wait indefinitely</param>
/// <returns>The notification result, or null if timed out or not found</returns>
public NotificationResult WaitForNotification(string notificationId, int timeout = -1)
{
return _toastService.WaitForNotification(notificationId, timeout);
}
/// <summary>
/// Gets all notification results from the database
/// </summary>
/// <returns>A list of notification results</returns>
public List<NotificationResult> GetAllNotificationResults() public List<NotificationResult> GetAllNotificationResults()
{ {
return _databaseService.GetAllNotificationResults(); return _databaseService.GetAllNotificationResults();
} }
/// <summary> /// <summary>
/// Deletes a notification result from the database /// Deletes a notification result from the database.
/// </summary> /// </summary>
/// <param name="notificationId">The ID of the notification</param> /// <param name="notificationId">The unique identifier of the notification.</param>
/// <returns>True if the deletion was successful, false otherwise</returns> /// <returns>True if the notification was deleted, false otherwise.</returns>
public bool DeleteNotificationResult(string notificationId) public bool DeleteNotificationResult(string notificationId)
{ {
return _databaseService.DeleteNotificationResult(notificationId); return _databaseService.DeleteNotificationResult(notificationId);
} }
/// <summary> /// <summary>
/// Deletes all notification results from the database /// Deletes all notification results from the database.
/// </summary> /// </summary>
/// <returns>True if the deletion was successful, false otherwise</returns> /// <returns>True if all notifications were deleted, false otherwise.</returns>
public bool DeleteAllNotificationResults() public bool DeleteAllNotificationResults()
{ {
return _databaseService.DeleteAllNotificationResults(); return _databaseService.DeleteAllNotificationResults();
} }
/// <summary> /// <summary>
/// Gets the path to the database file /// Gets the path to the database file.
/// </summary> /// </summary>
/// <returns>The database file path</returns> /// <returns>The path to the database file.</returns>
public string GetDatabaseFilePath() public string GetDatabaseFilePath()
{ {
return _databaseService.GetDatabaseFilePath(); return _databasePath;
} }
/// <summary> /// <summary>
/// Sets up a reminder timer for a notification /// Checks if the current process is running as SYSTEM.
/// </summary> /// </summary>
/// <param name="options">The notification options</param> /// <returns>True if running as SYSTEM, false otherwise.</returns>
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;
}
/// <summary>
/// Sets up a deadline timer for a notification
/// </summary>
/// <param name="options">The notification options</param>
/// <param name="result">The notification result</param>
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;
}
/// <summary>
/// Cleans up expired notifications from the database
/// </summary>
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}");
}
}
/// <summary>
/// Checks if the current process is running as SYSTEM
/// </summary>
/// <returns>True if running as SYSTEM, false otherwise</returns>
public bool IsRunningAsSystem() public bool IsRunningAsSystem()
{ {
return UserImpersonation.IsRunningAsSystem; return _sessionManager.IsRunningAsSystem();
} }
/// <summary> /// <summary>
/// Gets all interactive user sessions /// Gets all interactive user sessions.
/// </summary> /// </summary>
/// <returns>A list of interactive user sessions</returns> /// <returns>A list of interactive user sessions.</returns>
public List<string> GetInteractiveUserSessions() public List<string> GetInteractiveUserSessions()
{ {
return _sessionManager.GetInteractiveUserSessions() return _sessionManager.GetInteractiveUserSessions();
.Select(s => s.ToString()) }
.ToList();
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);
}
} }
} }
} }
@@ -6,11 +6,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle("WindowsNotifications")] [assembly: AssemblyTitle("WindowsNotifications")]
[assembly: AssemblyDescription("Windows Notification Library for PowerShell")] [assembly: AssemblyDescription("A library for displaying Windows notifications from SYSTEM context")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("WindowsNotifications")] [assembly: AssemblyProduct("WindowsNotifications")]
[assembly: AssemblyCopyright("Copyright © 2024")] [assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
Binary file not shown.
@@ -1,64 +1,61 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Linq;
using WindowsNotifications.Models; using WindowsNotifications.Models;
using WindowsNotifications.Utils; using WindowsNotifications.Utils;
namespace WindowsNotifications.Services namespace WindowsNotifications.Services
{ {
/// <summary> /// <summary>
/// Service for managing notification state persistence using LiteDB /// Service for managing notification data in LiteDB.
/// </summary> /// </summary>
internal class DatabaseService internal class DatabaseService
{ {
private readonly string _dbPath; private readonly string _databasePath;
private readonly LiteDBEmbedded _liteDb;
private const string DEFAULT_DB_FOLDER = "%PROGRAMDATA%\\WindowsNotifications";
private const string DEFAULT_DB_NAME = "notifications.db";
/// <summary> /// <summary>
/// Creates a new DatabaseService with the specified database path /// Initializes a new instance of the <see cref="DatabaseService"/> class.
/// </summary> /// </summary>
/// <param name="dbPath">The path to the database file, or null to use the default</param> /// <param name="databasePath">The path to the database file.</param>
public DatabaseService(string dbPath = null) public DatabaseService(string databasePath)
{ {
// Determine the database path _databasePath = databasePath;
_dbPath = GetDatabasePath(dbPath); EnsureDatabaseDirectoryExists();
// Ensure the directory exists
string directory = Path.GetDirectoryName(_dbPath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
// Initialize LiteDB
_liteDb = new LiteDBEmbedded(_dbPath);
} }
/// <summary> /// <summary>
/// Gets the database path, using the default if none is specified /// Saves a notification result to the database.
/// </summary> /// </summary>
/// <param name="dbPath">The specified database path, or null to use the default</param> /// <param name="result">The notification result to save.</param>
/// <returns>The resolved database path</returns> /// <returns>True if the result was saved successfully, false otherwise.</returns>
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);
}
/// <summary>
/// Saves a notification result to the database
/// </summary>
/// <param name="result">The notification result to save</param>
/// <returns>True if the save was successful, false otherwise</returns>
public bool SaveNotificationResult(NotificationResult result) public bool SaveNotificationResult(NotificationResult result)
{ {
try try
{ {
return _liteDb.UpsertNotificationResult(result); using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
{
var collection = db.GetCollection<NotificationResult>("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) catch (Exception)
{ {
@@ -67,15 +64,19 @@ namespace WindowsNotifications.Services
} }
/// <summary> /// <summary>
/// Gets a notification result from the database /// Gets a notification result from the database.
/// </summary> /// </summary>
/// <param name="notificationId">The ID of the notification</param> /// <param name="notificationId">The unique identifier of the notification.</param>
/// <returns>The notification result, or null if not found</returns> /// <returns>The notification result, or null if not found.</returns>
public NotificationResult GetNotificationResult(string notificationId) public NotificationResult GetNotificationResult(string notificationId)
{ {
try try
{ {
return _liteDb.GetNotificationResult(notificationId); using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
{
var collection = db.GetCollection<NotificationResult>("notifications");
return collection.FindOne(x => x.NotificationId == notificationId);
}
} }
catch (Exception) catch (Exception)
{ {
@@ -84,14 +85,18 @@ namespace WindowsNotifications.Services
} }
/// <summary> /// <summary>
/// Gets all notification results from the database /// Gets all notification results from the database.
/// </summary> /// </summary>
/// <returns>A list of notification results</returns> /// <returns>A list of notification results.</returns>
public List<NotificationResult> GetAllNotificationResults() public List<NotificationResult> GetAllNotificationResults()
{ {
try try
{ {
return _liteDb.GetAllNotificationResults(); using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
{
var collection = db.GetCollection<NotificationResult>("notifications");
return collection.FindAll().ToList();
}
} }
catch (Exception) catch (Exception)
{ {
@@ -100,15 +105,19 @@ namespace WindowsNotifications.Services
} }
/// <summary> /// <summary>
/// Deletes a notification result from the database /// Deletes a notification result from the database.
/// </summary> /// </summary>
/// <param name="notificationId">The ID of the notification</param> /// <param name="notificationId">The unique identifier of the notification.</param>
/// <returns>True if the deletion was successful, false otherwise</returns> /// <returns>True if the notification was deleted, false otherwise.</returns>
public bool DeleteNotificationResult(string notificationId) public bool DeleteNotificationResult(string notificationId)
{ {
try try
{ {
return _liteDb.DeleteNotificationResult(notificationId); using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
{
var collection = db.GetCollection<NotificationResult>("notifications");
return collection.Delete(x => x.NotificationId == notificationId) > 0;
}
} }
catch (Exception) catch (Exception)
{ {
@@ -117,14 +126,19 @@ namespace WindowsNotifications.Services
} }
/// <summary> /// <summary>
/// Deletes all notification results from the database /// Deletes all notification results from the database.
/// </summary> /// </summary>
/// <returns>True if the deletion was successful, false otherwise</returns> /// <returns>True if all notifications were deleted, false otherwise.</returns>
public bool DeleteAllNotificationResults() public bool DeleteAllNotificationResults()
{ {
try try
{ {
return _liteDb.DeleteAllNotificationResults(); using (var db = LiteDBEmbedded.GetDatabase(_databasePath))
{
var collection = db.GetCollection<NotificationResult>("notifications");
collection.DeleteAll();
return true;
}
} }
catch (Exception) catch (Exception)
{ {
@@ -132,13 +146,13 @@ namespace WindowsNotifications.Services
} }
} }
/// <summary> private void EnsureDatabaseDirectoryExists()
/// Gets the path to the database file
/// </summary>
/// <returns>The database file path</returns>
public string GetDatabaseFilePath()
{ {
return _dbPath; string directory = Path.GetDirectoryName(_databasePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
} }
} }
} }
@@ -1,370 +1,319 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using Windows.Data.Xml.Dom; using System.Windows.Forms;
using Windows.UI.Notifications; using System.Xml;
using WindowsNotifications.Models; using WindowsNotifications.Models;
namespace WindowsNotifications.Services namespace WindowsNotifications.Services
{ {
/// <summary> /// <summary>
/// Service for creating and displaying Windows Toast Notifications /// Service for displaying toast notifications.
/// </summary> /// </summary>
internal class ToastNotificationService internal class ToastNotificationService
{ {
private const string APP_ID = "WindowsNotifications.PowerShell";
private readonly Dictionary<string, ManualResetEvent> _notificationEvents = new Dictionary<string, ManualResetEvent>();
private readonly Dictionary<string, NotificationResult> _notificationResults = new Dictionary<string, NotificationResult>();
/// <summary> /// <summary>
/// Shows a toast notification with the specified options /// Shows a notification with the specified options.
/// </summary> /// </summary>
/// <param name="options">The notification options</param> /// <param name="options">The notification options.</param>
/// <returns>The result of the notification</returns> /// <returns>The notification result.</returns>
public NotificationResult ShowNotification(NotificationOptions options) public NotificationResult ShowNotification(NotificationOptions options)
{ {
var result = new NotificationResult(options.Id); var result = new NotificationResult
{
NotificationId = options.Id,
CreatedTime = DateTime.Now
};
try try
{ {
// Register COM server for notifications
RegisterComServer();
// Create the toast XML // Create the toast XML
string toastXml = CreateToastXml(options); string toastXml = GenerateToastXml(options);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(toastXml);
// Create the toast notification // Create a temporary file for the toast XML
ToastNotification toast = new ToastNotification(xmlDoc); string tempFile = Path.Combine(Path.GetTempPath(), $"toast_{options.Id}.xml");
File.WriteAllText(tempFile, toastXml);
// Set up event handlers // Create a temporary file for the result
var resetEvent = new ManualResetEvent(false); string resultFile = Path.Combine(Path.GetTempPath(), $"result_{options.Id}.json");
_notificationEvents[options.Id] = resetEvent;
_notificationResults[options.Id] = result;
// Register event handlers // Create the process arguments
toast.Activated += (sender, args) => OnToastActivated(sender, args, options); string arguments = $"-ToastFile \"{tempFile}\" -ResultFile \"{resultFile}\"";
toast.Dismissed += (sender, args) => OnToastDismissed(sender, args, options);
toast.Failed += (sender, args) => OnToastFailed(sender, args, options);
// Show the notification if (options.TimeoutInSeconds > 0)
ToastNotificationManager.CreateToastNotifier(APP_ID).Show(toast);
result.Displayed = true;
// If not async, wait for the notification to be interacted with
if (!options.Async)
{ {
resetEvent.WaitOne(); arguments += $" -Timeout {options.TimeoutInSeconds}";
result = _notificationResults[options.Id];
} }
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) catch (Exception ex)
{ {
return NotificationResult.Error(options.Id, ex.Message); result.Displayed = false;
result.ErrorMessage = ex.Message;
} }
return result;
} }
/// <summary> private string GenerateToastXml(NotificationOptions options)
/// Gets the result of a notification
/// </summary>
/// <param name="notificationId">The ID of the notification</param>
/// <returns>The notification result, or null if not found</returns>
public NotificationResult GetNotificationResult(string notificationId)
{ {
if (_notificationResults.ContainsKey(notificationId)) StringBuilder xml = new StringBuilder();
return _notificationResults[notificationId]; xml.AppendLine("<toast>");
xml.AppendLine(" <visual>");
return null; xml.AppendLine(" <binding template=\"ToastGeneric\">");
}
// Title
/// <summary> xml.AppendLine($" <text>{EscapeXml(options.Title)}</text>");
/// Waits for a notification to complete
/// </summary> // Message
/// <param name="notificationId">The ID of the notification</param> xml.AppendLine($" <text>{EscapeXml(options.Message)}</text>");
/// <param name="timeout">The timeout in milliseconds, or -1 to wait indefinitely</param>
/// <returns>The notification result, or null if timed out or not found</returns> // Logo image
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;
}
/// <summary>
/// Creates the XML for a toast notification
/// </summary>
/// <param name="options">The notification options</param>
/// <returns>The toast XML</returns>
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)
if (!string.IsNullOrEmpty(options.LogoImagePath)) if (!string.IsNullOrEmpty(options.LogoImagePath))
{ {
bool isUrl = options.LogoImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase); xml.AppendLine($" <image placement=\"appLogoOverride\" src=\"{EscapeXml(options.LogoImagePath)}\" />");
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);
}
} }
// Hero image (large banner image) // Hero image
if (!string.IsNullOrEmpty(options.HeroImagePath)) if (!string.IsNullOrEmpty(options.HeroImagePath))
{ {
bool isUrl = options.HeroImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase); xml.AppendLine($" <image placement=\"hero\" src=\"{EscapeXml(options.HeroImagePath)}\" />");
if (isUrl || File.Exists(options.HeroImagePath))
{
XmlElement heroElement = doc.CreateElement("image");
heroElement.SetAttribute("placement", "hero");
heroElement.SetAttribute("src", options.HeroImagePath);
bindingElement.AppendChild(heroElement);
}
} }
// Inline image (image within the notification content) // Attribution
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
if (!string.IsNullOrEmpty(options.Attribution)) if (!string.IsNullOrEmpty(options.Attribution))
{ {
XmlElement attributionElement = doc.CreateElement("text"); xml.AppendLine($" <text placement=\"attribution\">{EscapeXml(options.Attribution)}</text>");
attributionElement.SetAttribute("placement", "attribution");
attributionElement.InnerText = options.Attribution;
bindingElement.AppendChild(attributionElement);
} }
// Add branding text if specified xml.AppendLine(" </binding>");
if (!string.IsNullOrEmpty(options.BrandingText)) xml.AppendLine(" </visual>");
{
XmlElement brandingElement = doc.CreateElement("text"); // Actions
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
if (options.Buttons.Count > 0 || (options.DeferralOptions != null && options.DeferralOptions.Enabled)) if (options.Buttons.Count > 0 || (options.DeferralOptions != null && options.DeferralOptions.Enabled))
{ {
XmlElement actionsElement = doc.CreateElement("actions"); xml.AppendLine(" <actions>");
toastElement.AppendChild(actionsElement);
// Regular buttons
// 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
foreach (var button in options.Buttons) foreach (var button in options.Buttons)
{ {
XmlElement actionElement = doc.CreateElement("action"); xml.AppendLine($" <action content=\"{EscapeXml(button.Text)}\" arguments=\"{EscapeXml(button.Argument)}\" id=\"{EscapeXml(button.Id)}\" />");
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);
} }
}
// Deferral options
// Add audio element if specified if (options.DeferralOptions != null && options.DeferralOptions.Enabled)
if (!string.IsNullOrEmpty(options.AudioSource) || options.SilentMode)
{
XmlElement audioElement = doc.CreateElement("audio");
if (options.SilentMode)
{ {
audioElement.SetAttribute("silent", "true"); xml.AppendLine($" <action content=\"{EscapeXml(options.DeferralOptions.DeferButtonText)}\" arguments=\"defer\" id=\"defer\" />");
if (options.DeferralOptions.DeferralOptions.Count > 0)
{
xml.AppendLine($" <input id=\"deferTime\" type=\"selection\" title=\"{EscapeXml(options.DeferralOptions.DeferDropdownText)}\">");
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($" <selection id=\"{deferOption}\" content=\"{EscapeXml(text)}\" />");
}
xml.AppendLine(" </input>");
}
} }
else if (!string.IsNullOrEmpty(options.AudioSource))
{ xml.AppendLine(" </actions>");
audioElement.SetAttribute("src", options.AudioSource); }
if (options.LoopAudio) xml.AppendLine("</toast>");
audioElement.SetAttribute("loop", "true");
} return xml.ToString();
}
toastElement.AppendChild(audioElement);
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();
} }
/// <summary>
/// Escapes XML special characters
/// </summary>
/// <param name="text">The text to escape</param>
/// <returns>The escaped text</returns>
private string EscapeXml(string text) private string EscapeXml(string text)
{ {
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
return text return text
.Replace("&", "&amp;") .Replace("&", "&amp;")
.Replace("<", "&lt;") .Replace("<", "&lt;")
@@ -372,272 +321,5 @@ namespace WindowsNotifications.Services
.Replace("\"", "&quot;") .Replace("\"", "&quot;")
.Replace("'", "&apos;"); .Replace("'", "&apos;");
} }
/// <summary>
/// Handles toast activation events
/// </summary>
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();
}
/// <summary>
/// Handles toast dismissal events
/// </summary>
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();
}
/// <summary>
/// Handles toast failure events
/// </summary>
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();
}
/// <summary>
/// Logs a notification event
/// </summary>
/// <param name="options">The notification options</param>
/// <param name="eventType">The type of event</param>
/// <param name="details">Additional details about the event</param>
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}");
}
}
/// <summary>
/// Schedules a reminder for a deferred notification
/// </summary>
/// <param name="options">The notification options</param>
/// <param name="deferOption">The selected deferral option</param>
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}");
}
}
/// <summary>
/// Registers the COM server for notifications
/// </summary>
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);
} }
} }
+253 -258
View File
@@ -1,280 +1,275 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
using System.ComponentModel;
using System.Security.Permissions;
namespace WindowsNotifications.Services namespace WindowsNotifications.Services
{ {
/// <summary> /// <summary>
/// Provides functionality for impersonating a user from SYSTEM context /// Utility for impersonating users.
/// </summary> /// </summary>
internal class UserImpersonation : IDisposable internal static class UserImpersonation
{ {
/// <summary>
/// Runs an action as the specified user.
/// </summary>
/// <typeparam name="T">The return type of the action.</typeparam>
/// <param name="userName">The name of the user to impersonate.</param>
/// <param name="action">The action to run.</param>
/// <returns>The result of the action.</returns>
public static T RunAsUser<T>(string userName, Func<T> 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 #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)] [DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser( private static extern bool DuplicateTokenEx(
string lpszUsername, IntPtr hExistingToken,
string lpszDomain, int dwDesiredAccess,
string lpszPassword, IntPtr lpTokenAttributes,
int dwLogonType, SECURITY_IMPERSONATION_LEVEL impersonationLevel,
int dwLogonProvider, TOKEN_TYPE tokenType,
out IntPtr phToken); ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)] [DllImport("advapi32.dll", SetLastError = true)]
private static extern bool DuplicateToken( private static extern bool DuplicateToken(
IntPtr ExistingTokenHandle, IntPtr ExistingTokenHandle,
int ImpersonationLevel, int SECURITY_IMPERSONATION_LEVEL,
out IntPtr DuplicateTokenHandle); ref IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool ImpersonateLoggedOnUser(IntPtr hToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", SetLastError = true)] [DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject); private static extern bool CloseHandle(IntPtr hHandle);
[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;
#endregion #endregion
private IntPtr _userToken = IntPtr.Zero;
private IntPtr _impersonationToken = IntPtr.Zero;
private IntPtr _environmentBlock = IntPtr.Zero;
private bool _disposed = false;
private bool _isImpersonating = false;
/// <summary>
/// Gets whether the current process is running as SYSTEM
/// </summary>
public static bool IsRunningAsSystem
{
get
{
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
return identity.User.IsWellKnown(WellKnownSidType.LocalSystemSid);
}
}
}
/// <summary>
/// Impersonates a user by session ID
/// </summary>
/// <param name="sessionId">The session ID to impersonate</param>
/// <returns>True if impersonation was successful, false otherwise</returns>
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);
}
}
/// <summary>
/// Impersonates a user by username and password
/// </summary>
/// <param name="username">The username to impersonate</param>
/// <param name="domain">The domain of the user</param>
/// <param name="password">The password of the user</param>
/// <returns>True if impersonation was successful, false otherwise</returns>
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);
}
}
/// <summary>
/// Stops impersonating the user and reverts to the original security context
/// </summary>
public void StopImpersonation()
{
if (_isImpersonating)
{
RevertToSelf();
_isImpersonating = false;
}
CleanupTokens();
}
/// <summary>
/// Executes an action while impersonating a user
/// </summary>
/// <param name="sessionId">The session ID to impersonate</param>
/// <param name="action">The action to execute</param>
/// <returns>True if the action was executed successfully, false otherwise</returns>
public bool ExecuteAsUser(int sessionId, Action action)
{
if (!ImpersonateBySessionId(sessionId))
return false;
try
{
action();
return true;
}
finally
{
StopImpersonation();
}
}
/// <summary>
/// Executes a function while impersonating a user and returns the result
/// </summary>
/// <typeparam name="T">The return type of the function</typeparam>
/// <param name="sessionId">The session ID to impersonate</param>
/// <param name="func">The function to execute</param>
/// <returns>The result of the function</returns>
public T ExecuteAsUser<T>(int sessionId, Func<T> func)
{
if (!ImpersonateBySessionId(sessionId))
throw new Exception("Failed to impersonate user");
try
{
return func();
}
finally
{
StopImpersonation();
}
}
/// <summary>
/// Cleans up any open tokens
/// </summary>
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;
}
}
/// <summary>
/// Disposes of resources
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes of resources
/// </summary>
/// <param name="disposing">Whether this is being called from Dispose() or the finalizer</param>
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// Dispose managed resources
}
// Dispose unmanaged resources
StopImpersonation();
_disposed = true;
}
}
/// <summary>
/// Finalizer
/// </summary>
~UserImpersonation()
{
Dispose(false);
}
} }
} }
@@ -1,85 +1,166 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
using System.Diagnostics;
namespace WindowsNotifications.Services namespace WindowsNotifications.Services
{ {
/// <summary> /// <summary>
/// Manages user sessions and detects interactive sessions /// Service for managing user sessions and impersonation.
/// </summary> /// </summary>
internal class UserSessionManager internal class UserSessionManager
{ {
#region Win32 API /// <summary>
/// Checks if the current process is running as SYSTEM.
[DllImport("wtsapi32.dll")] /// </summary>
private static extern bool WTSEnumerateSessions( /// <returns>True if running as SYSTEM, false otherwise.</returns>
IntPtr hServer, public bool IsRunningAsSystem()
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
{ {
public int SessionId; using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
[MarshalAs(UnmanagedType.LPStr)] {
public string pWinStationName; return identity.IsSystem;
public WTS_CONNECTSTATE_CLASS State; }
} }
/// <summary>
/// Checks if there's an interactive user session.
/// </summary>
/// <returns>True if there's an interactive user session, false otherwise.</returns>
public bool HasInteractiveUserSession()
{
return GetInteractiveUserSessions().Count > 0;
}
/// <summary>
/// Gets all interactive user sessions.
/// </summary>
/// <returns>A list of interactive user sessions.</returns>
public List<string> GetInteractiveUserSessions()
{
List<string> sessions = new List<string>();
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;
}
/// <summary>
/// Runs an action as the interactive user.
/// </summary>
/// <typeparam name="T">The return type of the action.</typeparam>
/// <param name="action">The action to run.</param>
/// <returns>The result of the action.</returns>
public T RunAsInteractiveUser<T>(Func<T> action)
{
if (!IsRunningAsSystem())
{
return action();
}
List<string> 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 private enum WTS_INFO_CLASS
{ {
WTSInitialProgram = 0, WTSInitialProgram,
WTSApplicationName = 1, WTSApplicationName,
WTSWorkingDirectory = 2, WTSWorkingDirectory,
WTSOEMId = 3, WTSOEMId,
WTSSessionId = 4, WTSSessionId,
WTSUserName = 5, WTSUserName,
WTSWinStationName = 6, WTSWinStationName,
WTSDomainName = 7, WTSDomainName,
WTSConnectState = 8, WTSConnectState,
WTSClientBuildNumber = 9, WTSClientBuildNumber,
WTSClientName = 10, WTSClientName,
WTSClientDirectory = 11, WTSClientDirectory,
WTSClientProductId = 12, WTSClientProductId,
WTSClientHardwareId = 13, WTSClientHardwareId,
WTSClientAddress = 14, WTSClientAddress,
WTSClientDisplay = 15, WTSClientDisplay,
WTSClientProtocolType = 16, WTSClientProtocolType,
WTSIdleTime = 17, WTSIdleTime,
WTSLogonTime = 18, WTSLogonTime,
WTSIncomingBytes = 19, WTSIncomingBytes,
WTSOutgoingBytes = 20, WTSOutgoingBytes,
WTSIncomingFrames = 21, WTSIncomingFrames,
WTSOutgoingFrames = 22, WTSOutgoingFrames,
WTSClientInfo = 23, WTSClientInfo,
WTSSessionInfo = 24, WTSSessionInfo,
WTSSessionInfoEx = 25, WTSSessionInfoEx,
WTSConfigInfo = 26, WTSConfigInfo,
WTSValidationInfo = 27, WTSValidationInfo,
WTSSessionAddressV4 = 28, WTSSessionAddressV4,
WTSIsRemoteSession = 29 WTSIsRemoteSession
} }
public enum WTS_CONNECTSTATE_CLASS private enum WTS_CONNECTSTATE_CLASS
{ {
WTSActive, WTSActive,
WTSConnected, WTSConnected,
@@ -93,260 +174,34 @@ namespace WindowsNotifications.Services
WTSInit 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 #endregion
/// <summary>
/// Represents a user session
/// </summary>
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})";
}
}
/// <summary>
/// Gets all user sessions
/// </summary>
/// <returns>A list of user sessions</returns>
public List<UserSession> GetUserSessions()
{
var sessions = new List<UserSession>();
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;
}
/// <summary>
/// Gets all interactive user sessions (console or RDP)
/// </summary>
/// <returns>A list of interactive user sessions</returns>
public List<UserSession> GetInteractiveUserSessions()
{
return GetUserSessions().Where(s => s.State == WTS_CONNECTSTATE_CLASS.WTSActive).ToList();
}
/// <summary>
/// Gets the active console session, if any
/// </summary>
/// <returns>The active console session, or null if none exists</returns>
public UserSession GetActiveConsoleSession()
{
return GetUserSessions().FirstOrDefault(s => s.IsActiveConsoleSession);
}
/// <summary>
/// Gets the session information for the specified session ID and info class
/// </summary>
/// <param name="sessionId">The session ID</param>
/// <param name="infoClass">The information class to retrieve</param>
/// <returns>The session information</returns>
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;
}
/// <summary>
/// Determines if the specified session is a remote session
/// </summary>
/// <param name="sessionId">The session ID</param>
/// <returns>True if the session is remote, false otherwise</returns>
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;
}
/// <summary>
/// Gets the current interactive user information
/// </summary>
/// <returns>A UserSession object representing the current interactive user, or null if none exists</returns>
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;
}
}
/// <summary>
/// Gets the owner of a process
/// </summary>
/// <param name="processId">The process ID</param>
/// <returns>The process owner in the format DOMAIN\Username</returns>
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;
}
}
} }
} }
@@ -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
{
/// <summary>
/// Simple implementation of the Windows Notifications library
/// </summary>
public class SimpleNotificationManager
{
private readonly string _databasePath;
private readonly Dictionary<string, Timer> _reminderTimers = new Dictionary<string, Timer>();
/// <summary>
/// Gets or sets the path to the database file
/// </summary>
public string DatabasePath { get; private set; }
/// <summary>
/// Creates a new SimpleNotificationManager with the default database path
/// </summary>
public SimpleNotificationManager() : this(null)
{
}
/// <summary>
/// Creates a new SimpleNotificationManager with the specified database path
/// </summary>
/// <param name="databasePath">The path to the database file, or null to use the default</param>
public SimpleNotificationManager(string databasePath)
{
DatabasePath = databasePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "WindowsNotifications", "notifications.db");
}
/// <summary>
/// Shows a notification with the specified options
/// </summary>
/// <param name="options">The notification options</param>
/// <returns>The result of the notification</returns>
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;
}
/// <summary>
/// Shows a simple notification with the specified title and message
/// </summary>
/// <param name="title">The title of the notification</param>
/// <param name="message">The message body of the notification</param>
/// <returns>The result of the notification</returns>
public NotificationResult ShowSimpleNotification(string title, string message)
{
var options = new NotificationOptions
{
Title = title,
Message = message
};
return ShowNotification(options);
}
/// <summary>
/// Shows a notification with buttons
/// </summary>
/// <param name="title">The title of the notification</param>
/// <param name="message">The message body of the notification</param>
/// <param name="buttons">The buttons to display</param>
/// <returns>The result of the notification</returns>
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);
}
/// <summary>
/// Gets the result of a notification
/// </summary>
/// <param name="notificationId">The ID of the notification</param>
/// <returns>The notification result, or null if not found</returns>
public NotificationResult GetNotificationResult(string notificationId)
{
// In this simple implementation, we always return null
return null;
}
/// <summary>
/// Waits for a notification to complete
/// </summary>
/// <param name="notificationId">The ID of the notification</param>
/// <param name="timeout">The timeout in milliseconds, or -1 to wait indefinitely</param>
/// <returns>The notification result, or null if timed out or not found</returns>
public NotificationResult WaitForNotification(string notificationId, int timeout = -1)
{
// In this simple implementation, we always return null
return null;
}
/// <summary>
/// Gets all notification results
/// </summary>
/// <returns>A list of notification results</returns>
public List<NotificationResult> GetAllNotificationResults()
{
// In this simple implementation, we always return an empty list
return new List<NotificationResult>();
}
/// <summary>
/// Deletes a notification result
/// </summary>
/// <param name="notificationId">The ID of the notification</param>
/// <returns>True if the deletion was successful, false otherwise</returns>
public bool DeleteNotificationResult(string notificationId)
{
// In this simple implementation, we always return true
return true;
}
/// <summary>
/// Deletes all notification results
/// </summary>
/// <returns>True if the deletion was successful, false otherwise</returns>
public bool DeleteAllNotificationResults()
{
// In this simple implementation, we always return true
return true;
}
/// <summary>
/// Gets the path to the database file
/// </summary>
/// <returns>The database file path</returns>
public string GetDatabaseFilePath()
{
return DatabasePath;
}
/// <summary>
/// Checks if the current process is running as SYSTEM
/// </summary>
/// <returns>True if running as SYSTEM, false otherwise</returns>
public bool IsRunningAsSystem()
{
// In this simple implementation, we always return false
return false;
}
/// <summary>
/// Gets all interactive user sessions
/// </summary>
/// <returns>A list of interactive user sessions</returns>
public List<string> GetInteractiveUserSessions()
{
// In this simple implementation, we always return an empty list
return new List<string>();
}
}
}
@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A1B2C3D4-E5F6-47A8-B9C0-D1E2F3A4B5C6}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WindowsNotifications</RootNamespace>
<AssemblyName>WindowsNotifications</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<Compile Include="Models\SimpleNotificationOptions.cs" />
<Compile Include="Models\SimpleNotificationResult.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SimpleNotificationManager.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
+31 -68
View File
@@ -5,88 +5,51 @@ using System.Reflection;
namespace WindowsNotifications.Utils namespace WindowsNotifications.Utils
{ {
/// <summary> /// <summary>
/// Provides utilities for loading assemblies in PowerShell /// Utility for loading assemblies.
/// </summary> /// </summary>
public static class AssemblyLoader internal static class AssemblyLoader
{ {
/// <summary> /// <summary>
/// Gets the current assembly as a byte array /// Gets the bytes of an embedded resource.
/// </summary> /// </summary>
/// <returns>The assembly as a byte array</returns> /// <param name="resourceName">The name of the resource.</param>
public static byte[] GetAssemblyBytes() /// <returns>The bytes of the resource.</returns>
public static byte[] GetEmbeddedResourceBytes(string resourceName)
{ {
try Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{ {
string assemblyPath = Assembly.GetExecutingAssembly().Location; if (stream == null)
return File.ReadAllBytes(assemblyPath); {
} throw new Exception($"Resource {resourceName} not found.");
catch (Exception ex) }
{
throw new Exception("Failed to get assembly bytes", ex); byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
return buffer;
} }
} }
/// <summary> /// <summary>
/// Gets the current assembly as a Base64 encoded string /// Gets the text of an embedded resource.
/// </summary> /// </summary>
/// <returns>The assembly as a Base64 encoded string</returns> /// <param name="resourceName">The name of the resource.</param>
public static string GetAssemblyBase64() /// <returns>The text of the resource.</returns>
public static string GetEmbeddedResourceText(string resourceName)
{ {
try Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{ {
byte[] bytes = GetAssemblyBytes(); if (stream == null)
return Convert.ToBase64String(bytes); {
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);
}
}
/// <summary>
/// Gets a PowerShell script for loading the assembly
/// </summary>
/// <returns>A PowerShell script for loading the assembly</returns>
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)
";
} }
} }
} }
+23 -234
View File
@@ -1,259 +1,48 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using WindowsNotifications.Models;
namespace WindowsNotifications.Utils namespace WindowsNotifications.Utils
{ {
/// <summary> /// <summary>
/// Provides embedded LiteDB functionality /// Utility for working with embedded LiteDB.
/// </summary> /// </summary>
internal class LiteDBEmbedded internal static class LiteDBEmbedded
{ {
private readonly string _dbPath; private static readonly object _lockObject = new object();
private readonly Assembly _liteDbAssembly; private static 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;
/// <summary> /// <summary>
/// Creates a new LiteDBEmbedded instance with the specified database path /// Gets a LiteDB database instance.
/// </summary> /// </summary>
/// <param name="dbPath">The path to the database file</param> /// <param name="databasePath">The path to the database file.</param>
public LiteDBEmbedded(string dbPath) /// <returns>A LiteDB database instance.</returns>
public static dynamic GetDatabase(string databasePath)
{ {
_dbPath = dbPath; EnsureLiteDbLoaded();
// Load the embedded LiteDB assembly Type liteDbType = _liteDbAssembly.GetType("LiteDB.LiteDatabase");
_liteDbAssembly = LoadLiteDbAssembly(); return Activator.CreateInstance(liteDbType, databasePath);
// 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();
} }
/// <summary> private static void EnsureLiteDbLoaded()
/// Loads the embedded LiteDB assembly
/// </summary>
/// <returns>The LiteDB assembly</returns>
private Assembly LoadLiteDbAssembly()
{ {
try if (_liteDbAssembly != null)
{ {
// Try to load from embedded resource return;
string resourceName = "WindowsNotifications.Resources.LiteDB.dll"; }
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
lock (_lockObject)
{
if (_liteDbAssembly != null)
{ {
if (stream != null) return;
{
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
} }
// If not found as embedded resource, try to load from file // Load the embedded LiteDB assembly
return Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "LiteDB.dll")); byte[] liteDbBytes = AssemblyLoader.GetEmbeddedResourceBytes("WindowsNotifications.Resources.LiteDB.dll");
_liteDbAssembly = Assembly.Load(liteDbBytes);
} }
catch (Exception ex)
{
throw new Exception("Failed to load LiteDB assembly", ex);
}
}
/// <summary>
/// Registers the NotificationResult type with the BsonMapper
/// </summary>
private void RegisterNotificationResultType()
{
try
{
// Get the Entity method
MethodInfo entityMethod = _bsonMapper.GetMethod("Entity", new Type[] { });
// Create a generic Entity<NotificationResult> 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);
}
}
/// <summary>
/// Inserts or updates a notification result in the database
/// </summary>
/// <param name="result">The notification result to save</param>
/// <returns>True if the operation was successful, false otherwise</returns>
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;
}
}
/// <summary>
/// Gets a notification result from the database
/// </summary>
/// <param name="notificationId">The ID of the notification</param>
/// <returns>The notification result, or null if not found</returns>
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;
}
}
/// <summary>
/// Gets all notification results from the database
/// </summary>
/// <returns>A list of notification results</returns>
public List<NotificationResult> 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<NotificationResult>();
// 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<NotificationResult>();
}
}
/// <summary>
/// Deletes a notification result from the database
/// </summary>
/// <param name="notificationId">The ID of the notification</param>
/// <returns>True if the deletion was successful, false otherwise</returns>
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;
}
}
/// <summary>
/// Deletes all notification results from the database
/// </summary>
/// <returns>True if the deletion was successful, false otherwise</returns>
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;
}
}
/// <summary>
/// Creates a BsonValue from a string
/// </summary>
/// <param name="value">The string value</param>
/// <returns>A BsonValue object</returns>
private object CreateBsonValue(string value)
{
return Activator.CreateInstance(_bsonValue, new object[] { value });
} }
} }
} }
@@ -33,29 +33,42 @@
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"> <Reference Include="System.Drawing" />
<SpecificVersion>False</SpecificVersion> <Reference Include="System.Windows.Forms" />
<HintPath>C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Runtime.WindowsRuntime.dll</HintPath> <Reference Include="System.Xml.Linq" />
<Private>True</Private> <Reference Include="System.Data.DataSetExtensions" />
</Reference>
<Reference Include="Windows">
<HintPath>C:\Windows\Microsoft.NET\Framework\v4.0.30319\Windows.winmd</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="System.Management" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" /> <Reference Include="PresentationFramework" />
<Reference Include="System.Xaml" />
<Reference Include="System.Management.Automation" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\LiteDB.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="Cmdlets\ClearAllNotificationResultsCmdlet.cs" />
<Compile Include="Cmdlets\GetAllNotificationResultsCmdlet.cs" />
<Compile Include="Cmdlets\GetInteractiveUserSessionsCmdlet.cs" />
<Compile Include="Cmdlets\GetNotificationResultCmdlet.cs" />
<Compile Include="Cmdlets\InitializeWindowsNotificationsCmdlet.cs" />
<Compile Include="Cmdlets\RemoveNotificationResultCmdlet.cs" />
<Compile Include="Cmdlets\ShowNotificationCmdlet.cs" />
<Compile Include="Cmdlets\TestSystemContextCmdlet.cs" />
<Compile Include="Cmdlets\WaitNotificationCmdlet.cs" />
<Compile Include="NotificationManager.cs" />
<Compile Include="Models\DeadlineAction.cs" />
<Compile Include="Models\DeferralOptions.cs" /> <Compile Include="Models\DeferralOptions.cs" />
<Compile Include="Models\NotificationButton.cs" />
<Compile Include="Models\NotificationOptions.cs" /> <Compile Include="Models\NotificationOptions.cs" />
<Compile Include="Models\NotificationResult.cs" /> <Compile Include="Models\NotificationResult.cs" />
<Compile Include="NotificationManager.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\DatabaseService.cs" /> <Compile Include="Services\DatabaseService.cs" />
<Compile Include="Services\ToastNotificationService.cs" /> <Compile Include="Services\ToastNotificationService.cs" />
<Compile Include="Services\UserImpersonation.cs" /> <Compile Include="Services\UserImpersonation.cs" />
@@ -63,8 +76,5 @@
<Compile Include="Utils\AssemblyLoader.cs" /> <Compile Include="Utils\AssemblyLoader.cs" />
<Compile Include="Utils\LiteDBEmbedded.cs" /> <Compile Include="Utils\LiteDBEmbedded.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\LiteDB.dll" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>
+70 -28
View File
@@ -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 # Set the configuration
$Configuration = "Release" $configuration = if ($Release) { "Release" } else { "Debug" }
# Check if dotnet CLI is available # Set the MSBuild path
$dotnetPath = Get-Command dotnet -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source $msbuildPath = "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe"
if (-not $dotnetPath) { if (-not (Test-Path $msbuildPath)) {
Write-Host "The .NET SDK is not installed or not in the PATH. Please install the .NET SDK." -ForegroundColor Red Write-Error "MSBuild not found at $msbuildPath"
exit 1 exit 1
} }
# Clean the solution # Clean the solution if requested
Write-Host "Cleaning solution..." -ForegroundColor Cyan if ($Clean) {
dotnet clean WindowsNotifications.sln --configuration $Configuration Write-Host "Cleaning solution..."
& $msbuildPath "WindowsNotifications.sln" /t:Clean /p:Configuration=$configuration /v:minimal
# Restore NuGet packages if ($LASTEXITCODE -ne 0) {
Write-Host "Restoring NuGet packages..." -ForegroundColor Cyan Write-Error "Failed to clean solution"
dotnet restore WindowsNotifications.sln exit 1
}
}
# Build the solution # Build the solution
Write-Host "Building solution..." -ForegroundColor Cyan Write-Host "Building solution in $configuration configuration..."
dotnet build WindowsNotifications.sln --configuration $Configuration --no-restore & $msbuildPath "WindowsNotifications.sln" /t:Build /p:Configuration=$configuration /v:minimal
if ($LASTEXITCODE -ne 0) {
# Check if the build was successful Write-Error "Failed to build solution"
if ($LASTEXITCODE -eq 0) { exit 1
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
} }
# 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"
+3
View File
@@ -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
+9
View File
@@ -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
-33
View File
@@ -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
}
-37
View File
@@ -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
}
+8
View File
@@ -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
-50
View File
@@ -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