mirror of
https://github.com/freedbygrace/WindowsNotifications.git
synced 2026-07-26 12:08:14 +00:00
Add PowerShell cmdlets and update project structure
This commit is contained in:
@@ -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))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|||||||
@@ -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
@@ -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."
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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
|
||||||
@@ -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)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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")]
|
||||||
@@ -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>
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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("&", "&")
|
.Replace("&", "&")
|
||||||
.Replace("<", "<")
|
.Replace("<", "<")
|
||||||
@@ -372,272 +321,5 @@ namespace WindowsNotifications.Services
|
|||||||
.Replace("\"", """)
|
.Replace("\"", """)
|
||||||
.Replace("'", "'");
|
.Replace("'", "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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)
|
|
||||||
";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user