mirror of
https://github.com/freedbygrace/WindowsNotifications.git
synced 2026-07-26 12:08:14 +00:00
First Commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# Example: Asynchronous Notification
|
||||
# This script demonstrates how to show an asynchronous notification and check its status later
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# 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
|
||||
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 = $notificationManager.GetNotificationResult($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!"
|
||||
|
||||
# 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Background work 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) {
|
||||
Write-Host "Final notification state:"
|
||||
Write-Host " Activated: $($finalResult.Activated)"
|
||||
Write-Host " Dismissed: $($finalResult.Dismissed)"
|
||||
if ($finalResult.ClickedButtonId) {
|
||||
Write-Host " Button clicked: $($finalResult.ClickedButtonText)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
# Example: Countdown and Deadline Notification
|
||||
# This script demonstrates how to create a notification with a countdown timer and deadline action
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# 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."
|
||||
$options.PersistState = $true
|
||||
$options.EnableLogging = $true
|
||||
|
||||
# Set up logging
|
||||
$logFile = Join-Path -Path $PSScriptRoot -ChildPath "notification_log.txt"
|
||||
$options.LogAction = {
|
||||
param($logEntry)
|
||||
Add-Content -Path $logFile -Value $logEntry
|
||||
Write-Host $logEntry
|
||||
}
|
||||
|
||||
# Set deadline (5 minutes from now)
|
||||
$options.DeadlineTime = (Get-Date).AddMinutes(5)
|
||||
$options.ShowCountdown = $true
|
||||
|
||||
# Create deadline action (restart computer)
|
||||
$deadlineAction = [WindowsNotifications.Models.DeadlineAction]::ExecuteScript("Write-Host 'System would restart now (simulated)'; Start-Sleep -Seconds 5")
|
||||
$options.DeadlineAction = $deadlineAction
|
||||
|
||||
# Add buttons
|
||||
$restartButton = New-Object WindowsNotifications.Models.NotificationButton("Restart Now", "restart")
|
||||
$deferButton = New-Object WindowsNotifications.Models.NotificationButton("Defer", "defer")
|
||||
$options.Buttons.Add($restartButton)
|
||||
$options.Buttons.Add($deferButton)
|
||||
|
||||
# Configure deferral options
|
||||
$options.DeferralOptions = New-Object WindowsNotifications.Models.DeferralOptions
|
||||
$options.DeferralOptions.DeferButtonText = "Defer Restart"
|
||||
$options.DeferralOptions.DeferralPrompt = "Postpone until:"
|
||||
$options.DeferralOptions.MaxDeferrals = 2
|
||||
$options.DeferralOptions.EnforceMaxDeferrals = $true
|
||||
$options.DeferralOptions.ScheduleReminder = $true
|
||||
|
||||
# Clear existing deferral choices and add custom ones
|
||||
$options.DeferralOptions.DeferralChoices.Clear()
|
||||
$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("1 minute", [TimeSpan]::FromMinutes(1), "1min")))
|
||||
$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("2 minutes", [TimeSpan]::FromMinutes(2), "2min")))
|
||||
$options.DeferralOptions.DeferralChoices.Add((New-Object WindowsNotifications.Models.DeferralOption("3 minutes", [TimeSpan]::FromMinutes(3), "3min")))
|
||||
|
||||
# Set up event handlers
|
||||
$options.OnActivated = {
|
||||
param($result)
|
||||
$message = "Notification was activated with button: $($result.ClickedButtonText)"
|
||||
Write-Host $message
|
||||
Add-Content -Path $logFile -Value $message
|
||||
|
||||
if ($result.ClickedButtonId -eq "restart") {
|
||||
Write-Host "User chose to restart now (simulated)"
|
||||
# In a real script, you would restart the computer here
|
||||
# Restart-Computer -Force
|
||||
}
|
||||
}
|
||||
|
||||
$options.OnTimeout = {
|
||||
param($result)
|
||||
$message = "Notification timed out"
|
||||
Write-Host $message
|
||||
Add-Content -Path $logFile -Value $message
|
||||
}
|
||||
|
||||
$options.OnError = {
|
||||
param($result)
|
||||
$message = "Error occurred: $($result.ErrorMessage)"
|
||||
Write-Host $message -ForegroundColor Red
|
||||
Add-Content -Path $logFile -Value $message
|
||||
}
|
||||
|
||||
# Show the notification
|
||||
Write-Host "Showing countdown notification with deadline..."
|
||||
$result = $notificationManager.ShowNotification($options)
|
||||
|
||||
# Display the result
|
||||
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)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nLog file: $logFile"
|
||||
@@ -0,0 +1,105 @@
|
||||
# Example: Custom Branded Notification
|
||||
# This script demonstrates how to create a notification with custom branding
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# Create custom notification options with branding
|
||||
$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."
|
||||
|
||||
# Set branding properties
|
||||
$options.BrandingText = "Contoso IT Department"
|
||||
$options.BrandingColor = "#0078D7" # Blue
|
||||
$options.AccentColor = "#E81123" # Red
|
||||
$options.UseDarkTheme = $true
|
||||
|
||||
# Set custom images
|
||||
# Note: Replace these paths with actual image paths on your system
|
||||
$logoPath = Join-Path -Path $PSScriptRoot -ChildPath "Images\company-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"
|
||||
if (Test-Path $heroPath) {
|
||||
$options.HeroImagePath = $heroPath
|
||||
}
|
||||
|
||||
# Add custom buttons with images
|
||||
$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
|
||||
$options.Buttons.Add($updateButton)
|
||||
$options.Buttons.Add($deferButton)
|
||||
$options.Buttons.Add($moreInfoButton)
|
||||
|
||||
# Configure audio
|
||||
$options.AudioSource = "ms-winsoundevent:Notification.Default"
|
||||
$options.SilentMode = $false
|
||||
|
||||
# Show the notification
|
||||
Write-Host "Showing custom branded notification..."
|
||||
$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 ($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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# Example: Load Assembly 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
|
||||
$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."
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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
|
||||
}
|
||||
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# Show a notification with buttons
|
||||
Write-Host "Showing notification with buttons..."
|
||||
$result = $notificationManager.ShowNotificationWithButtons(
|
||||
"Action Required",
|
||||
"Please select an option below:",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
# Example: Using the WindowsNotifications PowerShell Module
|
||||
# This script demonstrates how to use the WindowsNotifications PowerShell module
|
||||
|
||||
# Import the module
|
||||
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath "..\PowerShell"
|
||||
Import-Module $modulePath -Force
|
||||
|
||||
# Initialize the module
|
||||
# Note: The DLL must be in the same directory as the module files
|
||||
$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
|
||||
}
|
||||
|
||||
# Check if running as SYSTEM
|
||||
$isSystem = Test-SystemContext
|
||||
Write-Host "Running as SYSTEM: $isSystem"
|
||||
|
||||
# Get interactive user sessions
|
||||
$sessions = Get-InteractiveUserSessions
|
||||
Write-Host "Interactive user sessions found: $($sessions.Count)"
|
||||
foreach ($session in $sessions) {
|
||||
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"
|
||||
|
||||
# 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" }
|
||||
}
|
||||
}
|
||||
|
||||
# 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"
|
||||
|
||||
# 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"
|
||||
@@ -0,0 +1,68 @@
|
||||
# Example: Reboot Notification with Deferrals
|
||||
# This script demonstrates how to show a reboot notification with deferral options
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# 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)
|
||||
|
||||
# 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 -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
# Example: Simple Notification
|
||||
# This script demonstrates how to show a simple notification
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# Check if running as SYSTEM
|
||||
$isSystem = $notificationManager.IsRunningAsSystem()
|
||||
Write-Host "Running as SYSTEM: $isSystem"
|
||||
|
||||
# Get interactive user sessions
|
||||
$sessions = $notificationManager.GetInteractiveUserSessions()
|
||||
Write-Host "Interactive user sessions found: $($sessions.Count)"
|
||||
foreach ($session in $sessions) {
|
||||
Write-Host " $session"
|
||||
}
|
||||
|
||||
# Show a simple notification
|
||||
Write-Host "Showing simple notification..."
|
||||
$result = $notificationManager.ShowSimpleNotification("Simple Notification", "This is a simple notification from PowerShell")
|
||||
|
||||
# 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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
# WindowsNotifications PowerShell Module
|
||||
|
||||
This PowerShell module provides a convenient way to use the Windows Notifications library from PowerShell scripts.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Copy the module files 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
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Initialize the Module
|
||||
|
||||
Before using the module, you need to initialize it:
|
||||
|
||||
```powershell
|
||||
# Initialize with default settings
|
||||
Initialize-WindowsNotifications
|
||||
|
||||
# Or initialize with a custom assembly path
|
||||
Initialize-WindowsNotifications -AssemblyPath "C:\Path\To\WindowsNotifications.dll"
|
||||
|
||||
# Or initialize with a custom database path
|
||||
Initialize-WindowsNotifications -DatabasePath "C:\Path\To\Notifications.db"
|
||||
```
|
||||
|
||||
### Show Notifications
|
||||
|
||||
#### Simple Notification
|
||||
|
||||
```powershell
|
||||
Show-Notification -Title "Hello" -Message "This is a simple notification"
|
||||
```
|
||||
|
||||
#### Notification with Buttons
|
||||
|
||||
```powershell
|
||||
$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer"
|
||||
|
||||
if ($result.ClickedButtonText -eq "Approve") {
|
||||
Write-Host "User approved the action"
|
||||
}
|
||||
```
|
||||
|
||||
#### Reboot Notification with Deferrals
|
||||
|
||||
```powershell
|
||||
$result = Show-Notification -Title "System Reboot Required" -Message "Your system needs to be rebooted." -RebootButtonText "Reboot Now" -DeferButtonText "Defer"
|
||||
|
||||
if ($result.ClickedButtonId -eq "reboot") {
|
||||
# Reboot the system
|
||||
Restart-Computer -Force
|
||||
}
|
||||
```
|
||||
|
||||
#### Advanced Options
|
||||
|
||||
```powershell
|
||||
Show-Notification -Title "Advanced Notification" -Message "This notification has advanced options" `
|
||||
-Async -PersistState -TimeoutInSeconds 30 -LogoImagePath "C:\Path\To\Logo.png" `
|
||||
-HeroImagePath "C:\Path\To\Hero.png" -ShowReminder -ReminderTimeInMinutes 5
|
||||
```
|
||||
|
||||
### Work with Notification Results
|
||||
|
||||
```powershell
|
||||
# Get the result of a notification
|
||||
$result = Get-NotificationResult -NotificationId "notification-id"
|
||||
|
||||
# Wait for a notification to complete
|
||||
$result = Wait-Notification -NotificationId "notification-id" -TimeoutInSeconds 30
|
||||
|
||||
# Get all notification history
|
||||
$history = Get-NotificationHistory
|
||||
|
||||
# Remove notification history
|
||||
Remove-NotificationHistory -NotificationId "notification-id"
|
||||
|
||||
# Remove all notification history
|
||||
Remove-NotificationHistory
|
||||
```
|
||||
|
||||
### System Information
|
||||
|
||||
```powershell
|
||||
# Check if running as SYSTEM
|
||||
$isSystem = Test-SystemContext
|
||||
|
||||
# Get interactive user sessions
|
||||
$sessions = Get-InteractiveUserSessions
|
||||
```
|
||||
|
||||
## Command Reference
|
||||
|
||||
| Command | Description |
|
||||
| ------- | ----------- |
|
||||
| `Initialize-WindowsNotifications` | Initializes the Windows Notifications module |
|
||||
| `Show-Notification` | Shows a notification with various options |
|
||||
| `Get-NotificationResult` | Gets the result of a notification |
|
||||
| `Wait-Notification` | Waits for a notification to complete |
|
||||
| `Get-NotificationHistory` | Gets all notification results from the database |
|
||||
| `Remove-NotificationHistory` | Deletes notification results from the database |
|
||||
| `Get-InteractiveUserSessions` | Gets all interactive user sessions |
|
||||
| `Test-SystemContext` | Checks if the current process is running as SYSTEM |
|
||||
|
||||
## Custom Branding
|
||||
|
||||
The module supports custom branding for notifications, allowing you to create notifications that match your organization's branding:
|
||||
|
||||
```powershell
|
||||
Show-Notification -Title "IT Department Notification" -Message "System update required" `
|
||||
-BrandingText "Contoso IT" `
|
||||
-BrandingColor "#0078D7" `
|
||||
-AccentColor "#E81123" `
|
||||
-LogoImagePath "C:\Path\To\Logo.png" `
|
||||
-HeroImagePath "C:\Path\To\Banner.png" `
|
||||
-UseDarkTheme
|
||||
```
|
||||
|
||||
### Supported Branding Options
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| `BrandingText` | Custom branding text (e.g., company or department name) |
|
||||
| `BrandingColor` | Custom branding color (hex format: #RRGGBB) |
|
||||
| `AccentColor` | Custom accent color for the notification (hex format: #RRGGBB) |
|
||||
| `UseDarkTheme` | Whether to use dark theme for the notification |
|
||||
| `LogoImagePath` | Path or URL to a logo image |
|
||||
| `HeroImagePath` | Path or URL to a hero/banner image |
|
||||
| `InlineImagePath` | Path or URL to an inline image |
|
||||
| `AppIconPath` | Path or URL to an app icon |
|
||||
| `BackgroundImagePath` | Path or URL to a background image |
|
||||
| `AudioSource` | Audio source for the notification |
|
||||
| `SilentMode` | Whether to show the notification in silent mode (no sound) |
|
||||
| `DeadlineTime` | Deadline time for the notification |
|
||||
| `ShowCountdown` | Whether to show a countdown timer on the notification |
|
||||
| `DeadlineActionCommand` | Command to execute when the deadline is reached |
|
||||
| `DeadlineActionArguments` | Arguments for the deadline command |
|
||||
| `DeadlineActionUrl` | URL to open when the deadline is reached |
|
||||
| `DeadlineActionScript` | PowerShell script to execute when the deadline is reached |
|
||||
| `EnableLogging` | Whether to enable logging for the notification |
|
||||
| `LogFilePath` | Path to the log file
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Simple Notification
|
||||
|
||||
```powershell
|
||||
Import-Module WindowsNotifications
|
||||
Initialize-WindowsNotifications
|
||||
|
||||
$result = Show-Notification -Title "Hello" -Message "This is a simple notification"
|
||||
Write-Host "Notification displayed: $($result.Displayed)"
|
||||
```
|
||||
|
||||
### Example 2: Notification with Buttons
|
||||
|
||||
```powershell
|
||||
Import-Module WindowsNotifications
|
||||
Initialize-WindowsNotifications
|
||||
|
||||
$result = Show-Notification -Title "Action Required" -Message "Please select an option:" -Buttons "Approve", "Reject", "Defer"
|
||||
|
||||
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" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Asynchronous Notification
|
||||
|
||||
```powershell
|
||||
Import-Module WindowsNotifications
|
||||
Initialize-WindowsNotifications
|
||||
|
||||
$result = Show-Notification -Title "Background Task" -Message "A background task is running" -Async
|
||||
|
||||
# Do some 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.Activated -or $currentResult.Dismissed) {
|
||||
Write-Host "User interacted with the notification"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Work completed"
|
||||
```
|
||||
|
||||
### Example 4: Countdown and Deadline Notification
|
||||
|
||||
```powershell
|
||||
Import-Module WindowsNotifications
|
||||
Initialize-WindowsNotifications
|
||||
|
||||
# Create a notification with a deadline 5 minutes from now
|
||||
$result = Show-Notification -Title "System Maintenance" -Message "Your system needs to restart soon." `
|
||||
-Buttons "Restart Now", "Defer" `
|
||||
-DeadlineTime (Get-Date).AddMinutes(5) `
|
||||
-ShowCountdown `
|
||||
-DeadlineActionScript "Restart-Computer -Force" `
|
||||
-EnableLogging `
|
||||
-LogFilePath "C:\Logs\notifications.log"
|
||||
|
||||
Write-Host "Notification displayed with deadline: $($result.Displayed)"
|
||||
```
|
||||
@@ -0,0 +1,125 @@
|
||||
#
|
||||
# Module manifest for module 'WindowsNotifications'
|
||||
#
|
||||
|
||||
@{
|
||||
# Script module or binary module file associated with this manifest.
|
||||
RootModule = 'WindowsNotifications.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '1.0.0'
|
||||
|
||||
# Supported PSEditions
|
||||
CompatiblePSEditions = @('Desktop')
|
||||
|
||||
# ID used to uniquely identify this module
|
||||
GUID = '8a1b3c4d-5e6f-47a8-b9c0-d1e2f3a4b5c6'
|
||||
|
||||
# Author of this module
|
||||
Author = 'WindowsNotifications'
|
||||
|
||||
# Company or vendor of this module
|
||||
CompanyName = ''
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) 2024. All rights reserved.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'PowerShell module for displaying Windows Toast Notifications from SYSTEM context to user sessions'
|
||||
|
||||
# Minimum version of the Windows PowerShell engine required by this module
|
||||
PowerShellVersion = '5.0'
|
||||
|
||||
# Name of the Windows PowerShell host required by this module
|
||||
# PowerShellHostName = ''
|
||||
|
||||
# Minimum version of the Windows PowerShell host required by this module
|
||||
# PowerShellHostVersion = ''
|
||||
|
||||
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
DotNetFrameworkVersion = '4.7.2'
|
||||
|
||||
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
|
||||
CLRVersion = '4.0'
|
||||
|
||||
# Processor architecture (None, X86, Amd64) required by this module
|
||||
# ProcessorArchitecture = ''
|
||||
|
||||
# Modules that must be imported into the global environment prior to importing this module
|
||||
# RequiredModules = @()
|
||||
|
||||
# Assemblies that must be loaded prior to importing this module
|
||||
# RequiredAssemblies = @()
|
||||
|
||||
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
|
||||
# ScriptsToProcess = @()
|
||||
|
||||
# Type files (.ps1xml) to be loaded when importing this module
|
||||
# TypesToProcess = @()
|
||||
|
||||
# Format files (.ps1xml) to be loaded when importing this module
|
||||
# FormatsToProcess = @()
|
||||
|
||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||
# NestedModules = @()
|
||||
|
||||
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
|
||||
FunctionsToExport = @(
|
||||
'Initialize-WindowsNotifications',
|
||||
'Show-Notification',
|
||||
'Get-NotificationResult',
|
||||
'Wait-Notification',
|
||||
'Get-NotificationHistory',
|
||||
'Remove-NotificationHistory',
|
||||
'Get-InteractiveUserSessions',
|
||||
'Test-SystemContext'
|
||||
)
|
||||
|
||||
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = '*'
|
||||
|
||||
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
|
||||
AliasesToExport = @()
|
||||
|
||||
# DSC resources to export from this module
|
||||
# DscResourcesToExport = @()
|
||||
|
||||
# List of all modules packaged with this module
|
||||
# ModuleList = @()
|
||||
|
||||
# List of all files packaged with this module
|
||||
FileList = @(
|
||||
'WindowsNotifications.psm1',
|
||||
'WindowsNotifications.psd1',
|
||||
'WindowsNotifications.dll'
|
||||
)
|
||||
|
||||
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
# Tags applied to this module. These help with module discovery in online galleries.
|
||||
Tags = @('Windows', 'Notifications', 'Toast', 'System')
|
||||
|
||||
# A URL to the license for this module.
|
||||
LicenseUri = 'https://github.com/freedbygrace/WindowsNotifications/blob/main/LICENSE'
|
||||
|
||||
# A URL to the main website for this project.
|
||||
ProjectUri = 'https://github.com/freedbygrace/WindowsNotifications'
|
||||
|
||||
# A URL to an icon representing this module.
|
||||
# IconUri = ''
|
||||
|
||||
# ReleaseNotes of this module
|
||||
ReleaseNotes = 'Initial release of the WindowsNotifications module'
|
||||
}
|
||||
}
|
||||
|
||||
# HelpInfo URI of this module
|
||||
# HelpInfoURI = ''
|
||||
|
||||
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
|
||||
# DefaultCommandPrefix = ''
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
#
|
||||
# WindowsNotifications.psm1
|
||||
# PowerShell module for the Windows Notifications library
|
||||
#
|
||||
|
||||
# Module variables
|
||||
$script:NotificationAssembly = $null
|
||||
$script:NotificationManager = $null
|
||||
$script:DefaultDatabasePath = $null
|
||||
|
||||
#
|
||||
# Private functions
|
||||
#
|
||||
|
||||
function Initialize-NotificationAssembly {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$AssemblyPath
|
||||
)
|
||||
|
||||
try {
|
||||
# If assembly path is provided and exists, load from file
|
||||
if ($AssemblyPath -and (Test-Path $AssemblyPath)) {
|
||||
$bytes = [System.IO.File]::ReadAllBytes($AssemblyPath)
|
||||
$script:NotificationAssembly = [System.Reflection.Assembly]::Load($bytes)
|
||||
}
|
||||
# Otherwise, try to load from the module directory
|
||||
else {
|
||||
$moduleRoot = Split-Path -Parent $PSCommandPath
|
||||
$defaultPath = Join-Path -Path $moduleRoot -ChildPath "WindowsNotifications.dll"
|
||||
|
||||
if (Test-Path $defaultPath) {
|
||||
$bytes = [System.IO.File]::ReadAllBytes($defaultPath)
|
||||
$script:NotificationAssembly = [System.Reflection.Assembly]::Load($bytes)
|
||||
}
|
||||
else {
|
||||
throw "WindowsNotifications.dll not found. Please specify the path using the -AssemblyPath parameter."
|
||||
}
|
||||
}
|
||||
|
||||
# Create the notification manager
|
||||
$script:NotificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
$script:DefaultDatabasePath = $script:NotificationManager.GetDatabaseFilePath()
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to initialize WindowsNotifications assembly: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Public functions
|
||||
#
|
||||
|
||||
function Initialize-WindowsNotifications {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$AssemblyPath,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$DatabasePath
|
||||
)
|
||||
|
||||
# Initialize the assembly
|
||||
if (-not (Initialize-NotificationAssembly -AssemblyPath $AssemblyPath)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# If a custom database path is specified, create a new notification manager with that path
|
||||
if ($DatabasePath) {
|
||||
try {
|
||||
$script:NotificationManager = New-Object WindowsNotifications.NotificationManager($DatabasePath)
|
||||
$script:DefaultDatabasePath = $script:NotificationManager.GetDatabaseFilePath()
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to initialize NotificationManager with custom database path: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "WindowsNotifications initialized successfully."
|
||||
Write-Host "Database path: $script:DefaultDatabasePath"
|
||||
|
||||
# Check if running as SYSTEM
|
||||
$isSystem = $script:NotificationManager.IsRunningAsSystem()
|
||||
Write-Host "Running as SYSTEM: $isSystem"
|
||||
|
||||
# Get interactive user sessions
|
||||
$sessions = $script:NotificationManager.GetInteractiveUserSessions()
|
||||
Write-Host "Interactive user sessions found: $($sessions.Count)"
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Show-Notification {
|
||||
[CmdletBinding(DefaultParameterSetName="Simple")]
|
||||
param (
|
||||
[Parameter(Mandatory=$true, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$true, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$true, ParameterSetName="Reboot")]
|
||||
[string]$Title,
|
||||
|
||||
[Parameter(Mandatory=$true, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$true, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$true, ParameterSetName="Reboot")]
|
||||
[string]$Message,
|
||||
|
||||
[Parameter(Mandatory=$true, ParameterSetName="WithButtons")]
|
||||
[string[]]$Buttons,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$RebootButtonText = "Reboot Now",
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$DeferButtonText = "Defer",
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$Async,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$PersistState,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[int]$TimeoutInSeconds = 0,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$LogoImagePath,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$HeroImagePath,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$InlineImagePath,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$AppIconPath,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$BackgroundImagePath,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$BrandingText,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$BrandingColor,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$AccentColor,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$UseDarkTheme,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$AudioSource,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$SilentMode,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$ShowReminder,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[int]$ReminderTimeInMinutes = 60,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[DateTime]$DeadlineTime,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$ShowCountdown,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$DeadlineActionCommand,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$DeadlineActionArguments,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$DeadlineActionUrl,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$DeadlineActionScript,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[switch]$EnableLogging,
|
||||
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Simple")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="WithButtons")]
|
||||
[Parameter(Mandatory=$false, ParameterSetName="Reboot")]
|
||||
[string]$LogFilePath
|
||||
)
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
# Handle different parameter sets
|
||||
switch ($PSCmdlet.ParameterSetName) {
|
||||
"Simple" {
|
||||
# Create custom notification options for simple notification
|
||||
$options = New-Object WindowsNotifications.Models.NotificationOptions
|
||||
$options.Title = $Title
|
||||
$options.Message = $Message
|
||||
$options.Async = $Async
|
||||
$options.PersistState = $PersistState
|
||||
$options.TimeoutInSeconds = $TimeoutInSeconds
|
||||
$options.ShowReminder = $ShowReminder
|
||||
$options.ReminderTimeInMinutes = $ReminderTimeInMinutes
|
||||
|
||||
# Set image paths
|
||||
if ($LogoImagePath) { $options.LogoImagePath = $LogoImagePath }
|
||||
if ($HeroImagePath) { $options.HeroImagePath = $HeroImagePath }
|
||||
if ($InlineImagePath) { $options.InlineImagePath = $InlineImagePath }
|
||||
if ($AppIconPath) { $options.AppIconPath = $AppIconPath }
|
||||
if ($BackgroundImagePath) { $options.BackgroundImagePath = $BackgroundImagePath }
|
||||
|
||||
# Set branding options
|
||||
if ($BrandingText) { $options.BrandingText = $BrandingText }
|
||||
if ($BrandingColor) { $options.BrandingColor = $BrandingColor }
|
||||
if ($AccentColor) { $options.AccentColor = $AccentColor }
|
||||
if ($UseDarkTheme) { $options.UseDarkTheme = $UseDarkTheme }
|
||||
|
||||
# Set audio options
|
||||
if ($AudioSource) { $options.AudioSource = $AudioSource }
|
||||
if ($SilentMode) { $options.SilentMode = $SilentMode }
|
||||
|
||||
# Set deadline and countdown options
|
||||
if ($DeadlineTime -ne $null) { $options.DeadlineTime = $DeadlineTime }
|
||||
if ($ShowCountdown) { $options.ShowCountdown = $ShowCountdown }
|
||||
|
||||
# Set deadline action
|
||||
if ($DeadlineActionCommand) {
|
||||
$options.DeadlineAction = New-Object WindowsNotifications.Models.DeadlineAction($DeadlineActionCommand, $DeadlineActionArguments)
|
||||
}
|
||||
elseif ($DeadlineActionUrl) {
|
||||
$options.DeadlineAction = [WindowsNotifications.Models.DeadlineAction]::OpenUrl($DeadlineActionUrl)
|
||||
}
|
||||
elseif ($DeadlineActionScript) {
|
||||
$options.DeadlineAction = [WindowsNotifications.Models.DeadlineAction]::ExecuteScript($DeadlineActionScript)
|
||||
}
|
||||
|
||||
# Set logging options
|
||||
if ($EnableLogging) { $options.EnableLogging = $true }
|
||||
if ($LogFilePath) {
|
||||
$options.LogAction = {
|
||||
param($logEntry)
|
||||
Add-Content -Path $LogFilePath -Value $logEntry
|
||||
}
|
||||
}
|
||||
|
||||
$result = $script:NotificationManager.ShowNotification($options)
|
||||
}
|
||||
"WithButtons" {
|
||||
# Create custom notification options for notification with buttons
|
||||
$options = New-Object WindowsNotifications.Models.NotificationOptions
|
||||
$options.Title = $Title
|
||||
$options.Message = $Message
|
||||
$options.Async = $Async
|
||||
$options.PersistState = $PersistState
|
||||
$options.TimeoutInSeconds = $TimeoutInSeconds
|
||||
$options.ShowReminder = $ShowReminder
|
||||
$options.ReminderTimeInMinutes = $ReminderTimeInMinutes
|
||||
|
||||
# Set image paths
|
||||
if ($LogoImagePath) { $options.LogoImagePath = $LogoImagePath }
|
||||
if ($HeroImagePath) { $options.HeroImagePath = $HeroImagePath }
|
||||
if ($InlineImagePath) { $options.InlineImagePath = $InlineImagePath }
|
||||
if ($AppIconPath) { $options.AppIconPath = $AppIconPath }
|
||||
if ($BackgroundImagePath) { $options.BackgroundImagePath = $BackgroundImagePath }
|
||||
|
||||
# Set branding options
|
||||
if ($BrandingText) { $options.BrandingText = $BrandingText }
|
||||
if ($BrandingColor) { $options.BrandingColor = $BrandingColor }
|
||||
if ($AccentColor) { $options.AccentColor = $AccentColor }
|
||||
if ($UseDarkTheme) { $options.UseDarkTheme = $UseDarkTheme }
|
||||
|
||||
# Set audio options
|
||||
if ($AudioSource) { $options.AudioSource = $AudioSource }
|
||||
if ($SilentMode) { $options.SilentMode = $SilentMode }
|
||||
|
||||
# Set deadline and countdown options
|
||||
if ($DeadlineTime -ne $null) { $options.DeadlineTime = $DeadlineTime }
|
||||
if ($ShowCountdown) { $options.ShowCountdown = $ShowCountdown }
|
||||
|
||||
# Set deadline action
|
||||
if ($DeadlineActionCommand) {
|
||||
$options.DeadlineAction = New-Object WindowsNotifications.Models.DeadlineAction($DeadlineActionCommand, $DeadlineActionArguments)
|
||||
}
|
||||
elseif ($DeadlineActionUrl) {
|
||||
$options.DeadlineAction = [WindowsNotifications.Models.DeadlineAction]::OpenUrl($DeadlineActionUrl)
|
||||
}
|
||||
elseif ($DeadlineActionScript) {
|
||||
$options.DeadlineAction = [WindowsNotifications.Models.DeadlineAction]::ExecuteScript($DeadlineActionScript)
|
||||
}
|
||||
|
||||
# Set logging options
|
||||
if ($EnableLogging) { $options.EnableLogging = $true }
|
||||
if ($LogFilePath) {
|
||||
$options.LogAction = {
|
||||
param($logEntry)
|
||||
Add-Content -Path $LogFilePath -Value $logEntry
|
||||
}
|
||||
}
|
||||
|
||||
# Add buttons
|
||||
foreach ($buttonText in $Buttons) {
|
||||
$button = New-Object WindowsNotifications.Models.NotificationButton($buttonText)
|
||||
$options.Buttons.Add($button)
|
||||
}
|
||||
|
||||
$result = $script:NotificationManager.ShowNotification($options)
|
||||
}
|
||||
"Reboot" {
|
||||
# Use the built-in reboot notification method
|
||||
$result = $script:NotificationManager.ShowRebootNotification($Title, $Message, $RebootButtonText, $DeferButtonText)
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to show notification: $_"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NotificationResult {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$NotificationId
|
||||
)
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $script:NotificationManager.GetNotificationResult($NotificationId)
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to get notification result: $_"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-Notification {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$NotificationId,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[int]$TimeoutInSeconds = -1
|
||||
)
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$timeoutMs = $TimeoutInSeconds -eq -1 ? -1 : ($TimeoutInSeconds * 1000)
|
||||
return $script:NotificationManager.WaitForNotification($NotificationId, $timeoutMs)
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to wait for notification: $_"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NotificationHistory {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $script:NotificationManager.GetAllNotificationResults()
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to get notification history: $_"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-NotificationHistory {
|
||||
[CmdletBinding()]
|
||||
param (
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$NotificationId
|
||||
)
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($NotificationId) {
|
||||
return $script:NotificationManager.DeleteNotificationResult($NotificationId)
|
||||
}
|
||||
else {
|
||||
return $script:NotificationManager.DeleteAllNotificationResults()
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to remove notification history: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-InteractiveUserSessions {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $script:NotificationManager.GetInteractiveUserSessions()
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to get interactive user sessions: $_"
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Test-SystemContext {
|
||||
[CmdletBinding()]
|
||||
param ()
|
||||
|
||||
# Ensure the notification assembly is initialized
|
||||
if (-not $script:NotificationManager) {
|
||||
if (-not (Initialize-WindowsNotifications)) {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return $script:NotificationManager.IsRunningAsSystem()
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to check system context: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Export public functions
|
||||
Export-ModuleMember -Function Initialize-WindowsNotifications, Show-Notification, Get-NotificationResult, Wait-Notification, Get-NotificationHistory, Remove-NotificationHistory, Get-InteractiveUserSessions, Test-SystemContext
|
||||
@@ -1 +1,375 @@
|
||||
# WindowsNotifications
|
||||
# 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.
|
||||
|
||||
## 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
|
||||
- **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
|
||||
- **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
|
||||
|
||||
## Requirements
|
||||
|
||||
- Windows 10 or later
|
||||
- .NET Framework 4.7.2 or later
|
||||
- PowerShell 5.0 or later
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the latest release from the [Releases](https://github.com/freedbygrace/WindowsNotifications/releases) page
|
||||
2. Extract the ZIP file to a location of your choice
|
||||
3. Load the assembly in your PowerShell script using one of the methods described below
|
||||
|
||||
## Usage
|
||||
|
||||
### Loading the Assembly
|
||||
|
||||
There are several ways to load the WindowsNotifications assembly in PowerShell:
|
||||
|
||||
#### Method 1: Load from file
|
||||
|
||||
```powershell
|
||||
$dllPath = "C:\Path\To\WindowsNotifications.dll"
|
||||
$bytes = [System.IO.File]::ReadAllBytes($dllPath)
|
||||
$assembly = [System.Reflection.Assembly]::Load($bytes)
|
||||
```
|
||||
|
||||
#### Method 2: Load from Base64 string
|
||||
|
||||
```powershell
|
||||
$base64 = "YOUR_BASE64_STRING_HERE" # Replace with the actual Base64 string of the DLL
|
||||
$bytes = [Convert]::FromBase64String($base64)
|
||||
$assembly = [System.Reflection.Assembly]::Load($bytes)
|
||||
```
|
||||
|
||||
### Basic Examples
|
||||
|
||||
#### Simple Notification
|
||||
|
||||
```powershell
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# Show a simple notification
|
||||
$result = $notificationManager.ShowSimpleNotification("Title", "Message")
|
||||
```
|
||||
|
||||
#### Notification with Buttons
|
||||
|
||||
```powershell
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# Show a notification with buttons
|
||||
$result = $notificationManager.ShowNotificationWithButtons(
|
||||
"Action Required",
|
||||
"Please select an option below:",
|
||||
"Approve",
|
||||
"Reject",
|
||||
"Defer"
|
||||
)
|
||||
|
||||
# Check which button was clicked
|
||||
if ($result.ClickedButtonId) {
|
||||
Write-Host "Button clicked: $($result.ClickedButtonText)"
|
||||
}
|
||||
```
|
||||
|
||||
#### Reboot Notification with Deferrals
|
||||
|
||||
```powershell
|
||||
# Create a notification manager
|
||||
$notificationManager = New-Object WindowsNotifications.NotificationManager
|
||||
|
||||
# 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
|
||||
|
||||
# Show a simple notification
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
See the [Examples](Examples) directory for complete PowerShell script examples:
|
||||
|
||||
- [SimpleNotification.ps1](Examples/SimpleNotification.ps1) - Shows a simple notification
|
||||
- [NotificationWithButtons.ps1](Examples/NotificationWithButtons.ps1) - Shows a notification with buttons
|
||||
- [RebootNotification.ps1](Examples/RebootNotification.ps1) - Shows a reboot notification with deferral options
|
||||
- [AsyncNotification.ps1](Examples/AsyncNotification.ps1) - Shows an asynchronous notification
|
||||
- [LoadFromBase64.ps1](Examples/LoadFromBase64.ps1) - Demonstrates loading the assembly from a Base64 string
|
||||
- [PowerShellModule.ps1](Examples/PowerShellModule.ps1) - Demonstrates using the PowerShell module
|
||||
- [CustomBrandedNotification.ps1](Examples/CustomBrandedNotification.ps1) - Demonstrates custom branding options
|
||||
- [CountdownAndDeadline.ps1](Examples/CountdownAndDeadline.ps1) - Demonstrates countdown timers and deadline actions
|
||||
|
||||
## 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
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F1E2D3C4-B5A6-47C8-B9D0-E1F2A3B4C5D6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace WindowsNotifications.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an action to take when a notification deadline is reached
|
||||
/// </summary>
|
||||
public class DeadlineAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of action to take
|
||||
/// </summary>
|
||||
public DeadlineActionType ActionType { get; set; } = DeadlineActionType.None;
|
||||
|
||||
/// <summary>
|
||||
/// The command to execute (for Process action type)
|
||||
/// </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)
|
||||
/// </summary>
|
||||
public string Script { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The custom action to execute (for Custom action type)
|
||||
/// </summary>
|
||||
public Action<NotificationResult> CustomAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new DeadlineAction with no action
|
||||
/// </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)
|
||||
{
|
||||
return new DeadlineAction
|
||||
{
|
||||
ActionType = DeadlineActionType.Url,
|
||||
Url = url
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new DeadlineAction that executes a PowerShell script
|
||||
/// </summary>
|
||||
/// <param name="script">The PowerShell script to execute</param>
|
||||
public static DeadlineAction ExecuteScript(string script)
|
||||
{
|
||||
return new DeadlineAction
|
||||
{
|
||||
ActionType = DeadlineActionType.Script,
|
||||
Script = script
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new DeadlineAction that executes a custom action
|
||||
/// </summary>
|
||||
/// <param name="action">The custom action to execute</param>
|
||||
public static DeadlineAction ExecuteCustomAction(Action<NotificationResult> action)
|
||||
{
|
||||
return new DeadlineAction
|
||||
{
|
||||
ActionType = DeadlineActionType.Custom,
|
||||
CustomAction = 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
|
||||
/// </summary>
|
||||
public enum DeadlineActionType
|
||||
{
|
||||
/// <summary>
|
||||
/// No action
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Run a process
|
||||
/// </summary>
|
||||
Process,
|
||||
|
||||
/// <summary>
|
||||
/// Open a URL
|
||||
/// </summary>
|
||||
Url,
|
||||
|
||||
/// <summary>
|
||||
/// Execute a PowerShell script
|
||||
/// </summary>
|
||||
Script,
|
||||
|
||||
/// <summary>
|
||||
/// Execute a custom action
|
||||
/// </summary>
|
||||
Custom
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WindowsNotifications.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for configuring notification deferrals
|
||||
/// </summary>
|
||||
public class DeferralOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether deferrals are enabled for this notification
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of times the notification can be deferred
|
||||
/// </summary>
|
||||
public int MaxDeferrals { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// The available deferral options to show to the user
|
||||
/// </summary>
|
||||
public List<DeferralOption> DeferralChoices { get; set; } = new List<DeferralOption>();
|
||||
|
||||
/// <summary>
|
||||
/// The text to display for the deferral button
|
||||
/// </summary>
|
||||
public string DeferButtonText { get; set; } = "Defer";
|
||||
|
||||
/// <summary>
|
||||
/// The text to display for the deferral selection prompt
|
||||
/// </summary>
|
||||
public string DeferralPrompt { get; set; } = "Defer until:";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to schedule a reminder when the notification is deferred
|
||||
/// </summary>
|
||||
public bool ScheduleReminder { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to enforce the maximum number of deferrals
|
||||
/// </summary>
|
||||
public bool EnforceMaxDeferrals { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The action to take when the maximum number of deferrals is reached
|
||||
/// </summary>
|
||||
public DeadlineAction MaxDeferralsAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current number of times this notification has been deferred
|
||||
/// </summary>
|
||||
public int CurrentDeferralCount { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new DeferralOptions instance with default values
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
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 or URL
|
||||
/// </summary>
|
||||
public string LogoImagePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional hero image path or URL
|
||||
/// </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
|
||||
/// </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 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
|
||||
/// </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>
|
||||
/// Optional deferral options for notifications that can be deferred
|
||||
/// </summary>
|
||||
public DeferralOptions DeferralOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show a reminder if the notification is not interacted with
|
||||
/// </summary>
|
||||
public bool ShowReminder { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Time in minutes after which to show a reminder if ShowReminder is true
|
||||
/// </summary>
|
||||
public int ReminderTimeInMinutes { get; set; } = 60;
|
||||
|
||||
/// <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>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public Action<NotificationResult> OnTimeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional action to execute when an error occurs
|
||||
/// </summary>
|
||||
public Action<NotificationResult> OnError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional 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>
|
||||
/// Whether to show a countdown timer on the notification
|
||||
/// </summary>
|
||||
public bool ShowCountdown { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Optional serialized data for reminders
|
||||
/// </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; }
|
||||
|
||||
/// <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>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
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>
|
||||
/// The error code, if an error occurred
|
||||
/// </summary>
|
||||
public string ErrorCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the notification was deferred
|
||||
/// </summary>
|
||||
public bool Deferred { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time when the notification was deferred until, if applicable
|
||||
/// </summary>
|
||||
public DateTime? DeferredUntil { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The reason for deferral, if applicable
|
||||
/// </summary>
|
||||
public string DeferralReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The reason for dismissal, if applicable
|
||||
/// </summary>
|
||||
public string DismissalReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The system action that was taken (e.g., snooze, dismiss)
|
||||
/// </summary>
|
||||
public string SystemAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the deadline was reached
|
||||
/// </summary>
|
||||
public bool DeadlineReached { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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
|
||||
/// </summary>
|
||||
public class NotificationManager
|
||||
{
|
||||
private readonly UserSessionManager _sessionManager;
|
||||
private readonly ToastNotificationService _toastService;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the LiteDB database file
|
||||
/// </summary>
|
||||
public string DatabasePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new NotificationManager with the default database path
|
||||
/// </summary>
|
||||
public NotificationManager() : this(null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new NotificationManager with the specified database path
|
||||
/// </summary>
|
||||
/// <param name="databasePath">The path to the LiteDB database file, or null to use the default</param>
|
||||
public NotificationManager(string databasePath)
|
||||
{
|
||||
DatabasePath = databasePath;
|
||||
_sessionManager = new UserSessionManager();
|
||||
_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));
|
||||
}
|
||||
|
||||
/// <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");
|
||||
|
||||
// Get interactive user sessions
|
||||
var sessions = _sessionManager.GetInteractiveUserSessions();
|
||||
if (sessions.Count == 0)
|
||||
return NotificationResult.Error(options.Id, "No interactive user sessions found");
|
||||
|
||||
// Show the notification in the first interactive session
|
||||
var session = sessions.First();
|
||||
NotificationResult result = null;
|
||||
|
||||
using (var impersonation = new UserImpersonation())
|
||||
{
|
||||
try
|
||||
{
|
||||
result = impersonation.ExecuteAsUser(session.SessionId, () => _toastService.ShowNotification(options));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result = NotificationResult.Error(options.Id, $"Failed to show notification: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Set up a reminder if requested
|
||||
if (options.ShowReminder && result.Displayed && !result.Activated && !result.Dismissed)
|
||||
{
|
||||
SetupReminderTimer(options);
|
||||
}
|
||||
|
||||
// Set up a deadline timer if specified
|
||||
if (options.DeadlineTime.HasValue && result.Displayed && !result.Activated && !result.Dismissed)
|
||||
{
|
||||
SetupDeadlineTimer(options, result);
|
||||
}
|
||||
|
||||
// Persist the result if requested
|
||||
if (options.PersistState && result != null)
|
||||
{
|
||||
_databaseService.SaveNotificationResult(result);
|
||||
}
|
||||
|
||||
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>
|
||||
/// 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>
|
||||
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
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
// Try to get from the toast service first
|
||||
var result = _toastService.GetNotificationResult(notificationId);
|
||||
|
||||
// If not found, try to get from the database
|
||||
if (result == null)
|
||||
result = _databaseService.GetNotificationResult(notificationId);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return _toastService.WaitForNotification(notificationId, timeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all notification results from the database
|
||||
/// </summary>
|
||||
/// <returns>A list of notification results</returns>
|
||||
public List<NotificationResult> GetAllNotificationResults()
|
||||
{
|
||||
return _databaseService.GetAllNotificationResults();
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return _databaseService.DeleteNotificationResult(notificationId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all notification results from the database
|
||||
/// </summary>
|
||||
/// <returns>True if the deletion was successful, false otherwise</returns>
|
||||
public bool DeleteAllNotificationResults()
|
||||
{
|
||||
return _databaseService.DeleteAllNotificationResults();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the database file
|
||||
/// </summary>
|
||||
/// <returns>The database file path</returns>
|
||||
public string GetDatabaseFilePath()
|
||||
{
|
||||
return _databaseService.GetDatabaseFilePath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up a reminder timer for a notification
|
||||
/// </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>
|
||||
public bool IsRunningAsSystem()
|
||||
{
|
||||
return UserImpersonation.IsRunningAsSystem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all interactive user sessions
|
||||
/// </summary>
|
||||
/// <returns>A list of interactive user sessions</returns>
|
||||
public List<string> GetInteractiveUserSessions()
|
||||
{
|
||||
return _sessionManager.GetInteractiveUserSessions()
|
||||
.Select(s => s.ToString())
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
[assembly: AssemblyDescription("Windows Notification Library for PowerShell")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("WindowsNotifications")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024")]
|
||||
[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("a1b2c3d4-e5f6-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")]
|
||||
Binary file not shown.
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using WindowsNotifications.Models;
|
||||
using WindowsNotifications.Utils;
|
||||
|
||||
namespace WindowsNotifications.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for managing notification state persistence using 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";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new DatabaseService with the specified database path
|
||||
/// </summary>
|
||||
/// <param name="dbPath">The path to the database file, or null to use the default</param>
|
||||
public DatabaseService(string dbPath = null)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the database path, using the default if none is specified
|
||||
/// </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>
|
||||
public bool SaveNotificationResult(NotificationResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _liteDb.UpsertNotificationResult(result);
|
||||
}
|
||||
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
|
||||
{
|
||||
return _liteDb.GetNotificationResult(notificationId);
|
||||
}
|
||||
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
|
||||
{
|
||||
return _liteDb.GetAllNotificationResults();
|
||||
}
|
||||
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
|
||||
{
|
||||
return _liteDb.DeleteNotificationResult(notificationId);
|
||||
}
|
||||
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
|
||||
{
|
||||
return _liteDb.DeleteAllNotificationResults();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the database file
|
||||
/// </summary>
|
||||
/// <returns>The database file path</returns>
|
||||
public string GetDatabaseFilePath()
|
||||
{
|
||||
return _dbPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Windows.Data.Xml.Dom;
|
||||
using Windows.UI.Notifications;
|
||||
using WindowsNotifications.Models;
|
||||
|
||||
namespace WindowsNotifications.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for creating and displaying Windows 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
|
||||
/// </summary>
|
||||
/// <param name="options">The notification options</param>
|
||||
/// <returns>The result of the notification</returns>
|
||||
public NotificationResult ShowNotification(NotificationOptions options)
|
||||
{
|
||||
var result = new NotificationResult(options.Id);
|
||||
|
||||
try
|
||||
{
|
||||
// Register COM server for notifications
|
||||
RegisterComServer();
|
||||
|
||||
// Create the toast XML
|
||||
string toastXml = CreateToastXml(options);
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(toastXml);
|
||||
|
||||
// Create the toast notification
|
||||
ToastNotification toast = new ToastNotification(xmlDoc);
|
||||
|
||||
// Set up event handlers
|
||||
var resetEvent = new ManualResetEvent(false);
|
||||
_notificationEvents[options.Id] = resetEvent;
|
||||
_notificationResults[options.Id] = result;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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)
|
||||
{
|
||||
resetEvent.WaitOne();
|
||||
result = _notificationResults[options.Id];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return NotificationResult.Error(options.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Hero image (large banner 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if (!string.IsNullOrEmpty(options.Attribution))
|
||||
{
|
||||
XmlElement attributionElement = doc.CreateElement("text");
|
||||
attributionElement.SetAttribute("placement", "attribution");
|
||||
attributionElement.InnerText = options.Attribution;
|
||||
bindingElement.AppendChild(attributionElement);
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Add audio element if specified
|
||||
if (!string.IsNullOrEmpty(options.AudioSource) || options.SilentMode)
|
||||
{
|
||||
XmlElement audioElement = doc.CreateElement("audio");
|
||||
|
||||
if (options.SilentMode)
|
||||
{
|
||||
audioElement.SetAttribute("silent", "true");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(options.AudioSource))
|
||||
{
|
||||
audioElement.SetAttribute("src", options.AudioSource);
|
||||
|
||||
if (options.LoopAudio)
|
||||
audioElement.SetAttribute("loop", "true");
|
||||
}
|
||||
|
||||
toastElement.AppendChild(audioElement);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return text
|
||||
.Replace("&", "&")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">")
|
||||
.Replace("\"", """)
|
||||
.Replace("'", "'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles toast activation events
|
||||
/// </summary>
|
||||
private void OnToastActivated(ToastNotification sender, object args, NotificationOptions options)
|
||||
{
|
||||
if (!_notificationResults.ContainsKey(options.Id))
|
||||
return;
|
||||
|
||||
var result = _notificationResults[options.Id];
|
||||
result.Activated = true;
|
||||
result.InteractionTime = DateTime.Now;
|
||||
|
||||
// Handle button clicks
|
||||
if (args is ToastActivatedEventArgs activatedArgs)
|
||||
{
|
||||
string argument = activatedArgs.Arguments;
|
||||
|
||||
// Check if this is a custom action
|
||||
if (argument == "snooze" || argument == "dismiss")
|
||||
{
|
||||
// Handle system actions
|
||||
result.SystemAction = argument;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle custom button clicks
|
||||
var button = options.Buttons.FirstOrDefault(b => b.Id == argument);
|
||||
|
||||
if (button != null)
|
||||
{
|
||||
result.ClickedButtonId = button.Id;
|
||||
result.ClickedButtonText = button.Text;
|
||||
result.ClickedButtonArgument = button.Argument;
|
||||
}
|
||||
|
||||
// Execute custom action if specified
|
||||
if (options.OnActivated != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
options.OnActivated(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error executing OnActivated action: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log the activation
|
||||
LogNotificationEvent(options, "Activated", result.ClickedButtonText ?? "Direct activation");
|
||||
|
||||
// Signal that the notification has been interacted with
|
||||
if (_notificationEvents.ContainsKey(options.Id))
|
||||
_notificationEvents[options.Id].Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles toast dismissal events
|
||||
/// </summary>
|
||||
private void OnToastDismissed(ToastNotification sender, ToastDismissedEventArgs args, NotificationOptions options)
|
||||
{
|
||||
if (!_notificationResults.ContainsKey(options.Id))
|
||||
return;
|
||||
|
||||
var result = _notificationResults[options.Id];
|
||||
result.Dismissed = true;
|
||||
result.InteractionTime = DateTime.Now;
|
||||
result.DismissalReason = args.Reason.ToString();
|
||||
|
||||
string dismissReason = "Unknown";
|
||||
|
||||
// Handle different dismissal reasons
|
||||
switch (args.Reason)
|
||||
{
|
||||
case ToastDismissalReason.ApplicationHidden:
|
||||
dismissReason = "Application hidden";
|
||||
break;
|
||||
|
||||
case ToastDismissalReason.UserCanceled:
|
||||
dismissReason = "User dismissed";
|
||||
|
||||
// Handle deferrals
|
||||
if (options.DeferralOptions != null && options.DeferralOptions.Enabled)
|
||||
{
|
||||
// Get the selected deferral option
|
||||
if (args is ToastDismissedEventArgs deferArgs && deferArgs.GetDeferralSelectionId() != null)
|
||||
{
|
||||
string deferralId = deferArgs.GetDeferralSelectionId();
|
||||
var deferOption = options.DeferralOptions.DeferralChoices.FirstOrDefault(d => d.Id == deferralId);
|
||||
|
||||
if (deferOption != null)
|
||||
{
|
||||
result.Deferred = true;
|
||||
result.DeferredUntil = DateTime.Now.Add(deferOption.DeferralTime);
|
||||
result.DeferralReason = deferOption.Text;
|
||||
dismissReason = $"Deferred until {result.DeferredUntil} ({deferOption.Text})";
|
||||
|
||||
// Schedule a reminder if needed
|
||||
if (options.DeferralOptions.ScheduleReminder)
|
||||
{
|
||||
ScheduleDeferralReminder(options, deferOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ToastDismissalReason.TimedOut:
|
||||
dismissReason = "Timed out";
|
||||
|
||||
// Execute timeout action if specified
|
||||
if (options.OnTimeout != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
options.OnTimeout(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error executing OnTimeout action: {ex.Message}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Log the dismissal
|
||||
LogNotificationEvent(options, "Dismissed", dismissReason);
|
||||
|
||||
// Signal that the notification has been interacted with
|
||||
if (_notificationEvents.ContainsKey(options.Id))
|
||||
_notificationEvents[options.Id].Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles toast failure events
|
||||
/// </summary>
|
||||
private void OnToastFailed(ToastNotification sender, ToastFailedEventArgs args, NotificationOptions options)
|
||||
{
|
||||
if (!_notificationResults.ContainsKey(options.Id))
|
||||
return;
|
||||
|
||||
var result = _notificationResults[options.Id];
|
||||
result.ErrorMessage = args.ErrorMessage;
|
||||
result.ErrorCode = args.ErrorCode.ToString();
|
||||
|
||||
// Log the failure
|
||||
LogNotificationEvent(options, "Failed", $"Error: {args.ErrorMessage} (Code: {args.ErrorCode})");
|
||||
|
||||
// Execute error action if specified
|
||||
if (options.OnError != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
options.OnError(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error executing OnError action: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Signal that the notification has failed
|
||||
if (_notificationEvents.ContainsKey(options.Id))
|
||||
_notificationEvents[options.Id].Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a notification event
|
||||
/// </summary>
|
||||
/// <param name="options">The notification options</param>
|
||||
/// <param name="eventType">The type of event</param>
|
||||
/// <param name="details">Additional details about the event</param>
|
||||
private void LogNotificationEvent(NotificationOptions options, string eventType, string details)
|
||||
{
|
||||
if (!options.EnableLogging)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the current user session
|
||||
var sessionManager = new UserSessionManager();
|
||||
var currentUser = sessionManager.GetCurrentInteractiveUser();
|
||||
string userInfo = currentUser != null ? $"{currentUser.DomainName}\\{currentUser.UserName} (Session: {currentUser.SessionId})" : "Unknown user";
|
||||
|
||||
// Create the log entry
|
||||
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{eventType}] ID: {options.Id}, Title: {options.Title}, User: {userInfo}, Details: {details}";
|
||||
|
||||
// Log to the specified action
|
||||
if (options.LogAction != null)
|
||||
{
|
||||
options.LogAction(logEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default logging to debug output
|
||||
System.Diagnostics.Debug.WriteLine(logEntry);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error logging notification event: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules a reminder for a deferred notification
|
||||
/// </summary>
|
||||
/// <param name="options">The notification options</param>
|
||||
/// <param name="deferOption">The selected deferral option</param>
|
||||
private void ScheduleDeferralReminder(NotificationOptions options, DeferralOption deferOption)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Calculate the reminder time
|
||||
DateTime reminderTime = DateTime.Now.Add(deferOption.DeferralTime);
|
||||
|
||||
// Create a task scheduler task
|
||||
using (TaskService ts = new TaskService())
|
||||
{
|
||||
// Create a new task definition
|
||||
TaskDefinition td = ts.NewTask();
|
||||
td.RegistrationInfo.Description = $"Reminder for notification: {options.Title}";
|
||||
|
||||
// Create a trigger that will fire at the reminder time
|
||||
td.Triggers.Add(new TimeTrigger(reminderTime));
|
||||
|
||||
// Create an action to show the reminder notification
|
||||
string actionPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
|
||||
string actionArgs = $"-ExecutionPolicy Bypass -Command \"& {{ $bytes = [System.Convert]::FromBase64String('{options.ReminderData}'); $assembly = [System.Reflection.Assembly]::Load($bytes); $notificationManager = New-Object WindowsNotifications.NotificationManager; $options = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::Deserialize([System.IO.MemoryStream]::new($bytes)); $notificationManager.ShowNotification($options); }}\"";
|
||||
|
||||
td.Actions.Add(new ExecAction(actionPath, actionArgs, null));
|
||||
|
||||
// Register the task
|
||||
ts.RootFolder.RegisterTaskDefinition($"WindowsNotification_Reminder_{options.Id}", td);
|
||||
}
|
||||
|
||||
// Log the scheduled reminder
|
||||
LogNotificationEvent(options, "ReminderScheduled", $"Reminder scheduled for {reminderTime}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error scheduling deferral reminder: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the COM server for notifications
|
||||
/// </summary>
|
||||
private void RegisterComServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Register the AppID for the current process
|
||||
string appUserModelId = APP_ID;
|
||||
SetCurrentProcessExplicitAppUserModelID(appUserModelId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Failed to register COM server for notifications", ex);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
private static extern int SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
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
|
||||
/// </summary>
|
||||
internal class UserImpersonation : IDisposable
|
||||
{
|
||||
#region Win32 API
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern bool LogonUser(
|
||||
string lpszUsername,
|
||||
string lpszDomain,
|
||||
string lpszPassword,
|
||||
int dwLogonType,
|
||||
int dwLogonProvider,
|
||||
out IntPtr phToken);
|
||||
|
||||
[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();
|
||||
|
||||
[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;
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace WindowsNotifications.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages user sessions and detects interactive sessions
|
||||
/// </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
|
||||
{
|
||||
public int SessionId;
|
||||
[MarshalAs(UnmanagedType.LPStr)]
|
||||
public string pWinStationName;
|
||||
public WTS_CONNECTSTATE_CLASS State;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
public enum WTS_CONNECTSTATE_CLASS
|
||||
{
|
||||
WTSActive,
|
||||
WTSConnected,
|
||||
WTSConnectQuery,
|
||||
WTSShadow,
|
||||
WTSDisconnected,
|
||||
WTSIdle,
|
||||
WTSListen,
|
||||
WTSReset,
|
||||
WTSDown,
|
||||
WTSInit
|
||||
}
|
||||
|
||||
private const int WTS_CURRENT_SERVER_HANDLE = 0;
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace WindowsNotifications.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides utilities for loading assemblies in PowerShell
|
||||
/// </summary>
|
||||
public static class AssemblyLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current assembly as a byte array
|
||||
/// </summary>
|
||||
/// <returns>The assembly as a byte array</returns>
|
||||
public static byte[] GetAssemblyBytes()
|
||||
{
|
||||
try
|
||||
{
|
||||
string assemblyPath = Assembly.GetExecutingAssembly().Location;
|
||||
return File.ReadAllBytes(assemblyPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Failed to get assembly bytes", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current assembly as a Base64 encoded string
|
||||
/// </summary>
|
||||
/// <returns>The assembly as a Base64 encoded string</returns>
|
||||
public static string GetAssemblyBase64()
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = GetAssemblyBytes();
|
||||
return Convert.ToBase64String(bytes);
|
||||
}
|
||||
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)
|
||||
";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using WindowsNotifications.Models;
|
||||
|
||||
namespace WindowsNotifications.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides embedded LiteDB functionality
|
||||
/// </summary>
|
||||
internal 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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new LiteDBEmbedded instance with the specified database path
|
||||
/// </summary>
|
||||
/// <param name="dbPath">The path to the database file</param>
|
||||
public LiteDBEmbedded(string dbPath)
|
||||
{
|
||||
_dbPath = dbPath;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the embedded LiteDB assembly
|
||||
/// </summary>
|
||||
/// <returns>The LiteDB assembly</returns>
|
||||
private Assembly LoadLiteDbAssembly()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to load from embedded resource
|
||||
string resourceName = "WindowsNotifications.Resources.LiteDB.dll";
|
||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (stream != null)
|
||||
{
|
||||
byte[] assemblyData = new byte[stream.Length];
|
||||
stream.Read(assemblyData, 0, assemblyData.Length);
|
||||
return Assembly.Load(assemblyData);
|
||||
}
|
||||
}
|
||||
|
||||
// If not found as embedded resource, try to load from file
|
||||
return Assembly.LoadFrom(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "LiteDB.dll"));
|
||||
}
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Windows">
|
||||
<HintPath>$(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\10.0.19041.0\Windows.winmd</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Models\DeferralOptions.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" />
|
||||
<Compile Include="Services\UserSessionManager.cs" />
|
||||
<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>
|
||||
@@ -0,0 +1,46 @@
|
||||
# Build script for WindowsNotifications
|
||||
|
||||
# Set the configuration
|
||||
$Configuration = "Release"
|
||||
|
||||
# Find MSBuild
|
||||
$msbuildPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\MSBuild.exe"
|
||||
if (-not (Test-Path $msbuildPath)) {
|
||||
$msbuildPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"
|
||||
}
|
||||
if (-not (Test-Path $msbuildPath)) {
|
||||
$msbuildPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe"
|
||||
}
|
||||
if (-not (Test-Path $msbuildPath)) {
|
||||
$msbuildPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"
|
||||
}
|
||||
if (-not (Test-Path $msbuildPath)) {
|
||||
# Try to find MSBuild in the PATH
|
||||
$msbuildPath = "MSBuild.exe"
|
||||
}
|
||||
|
||||
# Clean the solution
|
||||
Write-Host "Cleaning solution..." -ForegroundColor Cyan
|
||||
& $msbuildPath WindowsNotifications.sln /t:Clean /p:Configuration=$Configuration
|
||||
|
||||
# Restore NuGet packages
|
||||
Write-Host "Restoring NuGet packages..." -ForegroundColor Cyan
|
||||
& $msbuildPath WindowsNotifications.sln /t:Restore /p:Configuration=$Configuration
|
||||
|
||||
# Build the solution
|
||||
Write-Host "Building solution..." -ForegroundColor Cyan
|
||||
& $msbuildPath WindowsNotifications.sln /t:Build /p:Configuration=$Configuration
|
||||
|
||||
# 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
|
||||
}
|
||||
Reference in New Issue
Block a user