Add PowerShell cmdlets and update project structure

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