First Commit

This commit is contained in:
GraceSolutions
2025-04-10 16:01:19 -04:00
parent d7bd59c035
commit 2a80e82288
28 changed files with 4936 additions and 1 deletions
+84
View File
@@ -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)"
}
}
+125
View File
@@ -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"
+105
View File
@@ -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
}
}
}
}
}
+35
View File
@@ -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."
}
+58
View File
@@ -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"
}
}
}
}
}
+116
View File
@@ -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"
+68
View File
@@ -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)"
}
}
}
+49
View File
@@ -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"
}
}
+224
View File
@@ -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)"
```
+125
View File
@@ -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 = ''
}
+519
View File
@@ -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
+375 -1
View File
@@ -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.
+24
View File
@@ -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
};
}
}
}
+387
View File
@@ -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("&", "&amp;")
.Replace("<", "&lt;")
.Replace(">", "&gt;")
.Replace("\"", "&quot;")
.Replace("'", "&apos;");
}
/// <summary>
/// Handles toast activation events
/// </summary>
private void OnToastActivated(ToastNotification sender, object args, NotificationOptions options)
{
if (!_notificationResults.ContainsKey(options.Id))
return;
var result = _notificationResults[options.Id];
result.Activated = true;
result.InteractionTime = DateTime.Now;
// Handle button clicks
if (args is ToastActivatedEventArgs activatedArgs)
{
string argument = activatedArgs.Arguments;
// Check if this is a custom action
if (argument == "snooze" || argument == "dismiss")
{
// Handle system actions
result.SystemAction = argument;
}
else
{
// Handle custom button clicks
var button = options.Buttons.FirstOrDefault(b => b.Id == argument);
if (button != null)
{
result.ClickedButtonId = button.Id;
result.ClickedButtonText = button.Text;
result.ClickedButtonArgument = button.Argument;
}
// Execute custom action if specified
if (options.OnActivated != null)
{
try
{
options.OnActivated(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error executing OnActivated action: {ex.Message}");
}
}
}
}
// Log the activation
LogNotificationEvent(options, "Activated", result.ClickedButtonText ?? "Direct activation");
// Signal that the notification has been interacted with
if (_notificationEvents.ContainsKey(options.Id))
_notificationEvents[options.Id].Set();
}
/// <summary>
/// Handles toast dismissal events
/// </summary>
private void OnToastDismissed(ToastNotification sender, ToastDismissedEventArgs args, NotificationOptions options)
{
if (!_notificationResults.ContainsKey(options.Id))
return;
var result = _notificationResults[options.Id];
result.Dismissed = true;
result.InteractionTime = DateTime.Now;
result.DismissalReason = args.Reason.ToString();
string dismissReason = "Unknown";
// Handle different dismissal reasons
switch (args.Reason)
{
case ToastDismissalReason.ApplicationHidden:
dismissReason = "Application hidden";
break;
case ToastDismissalReason.UserCanceled:
dismissReason = "User dismissed";
// Handle deferrals
if (options.DeferralOptions != null && options.DeferralOptions.Enabled)
{
// Get the selected deferral option
if (args is ToastDismissedEventArgs deferArgs && deferArgs.GetDeferralSelectionId() != null)
{
string deferralId = deferArgs.GetDeferralSelectionId();
var deferOption = options.DeferralOptions.DeferralChoices.FirstOrDefault(d => d.Id == deferralId);
if (deferOption != null)
{
result.Deferred = true;
result.DeferredUntil = DateTime.Now.Add(deferOption.DeferralTime);
result.DeferralReason = deferOption.Text;
dismissReason = $"Deferred until {result.DeferredUntil} ({deferOption.Text})";
// Schedule a reminder if needed
if (options.DeferralOptions.ScheduleReminder)
{
ScheduleDeferralReminder(options, deferOption);
}
}
}
}
break;
case ToastDismissalReason.TimedOut:
dismissReason = "Timed out";
// Execute timeout action if specified
if (options.OnTimeout != null)
{
try
{
options.OnTimeout(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error executing OnTimeout action: {ex.Message}");
}
}
break;
}
// Log the dismissal
LogNotificationEvent(options, "Dismissed", dismissReason);
// Signal that the notification has been interacted with
if (_notificationEvents.ContainsKey(options.Id))
_notificationEvents[options.Id].Set();
}
/// <summary>
/// Handles toast failure events
/// </summary>
private void OnToastFailed(ToastNotification sender, ToastFailedEventArgs args, NotificationOptions options)
{
if (!_notificationResults.ContainsKey(options.Id))
return;
var result = _notificationResults[options.Id];
result.ErrorMessage = args.ErrorMessage;
result.ErrorCode = args.ErrorCode.ToString();
// Log the failure
LogNotificationEvent(options, "Failed", $"Error: {args.ErrorMessage} (Code: {args.ErrorCode})");
// Execute error action if specified
if (options.OnError != null)
{
try
{
options.OnError(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error executing OnError action: {ex.Message}");
}
}
// Signal that the notification has failed
if (_notificationEvents.ContainsKey(options.Id))
_notificationEvents[options.Id].Set();
}
/// <summary>
/// Logs a notification event
/// </summary>
/// <param name="options">The notification options</param>
/// <param name="eventType">The type of event</param>
/// <param name="details">Additional details about the event</param>
private void LogNotificationEvent(NotificationOptions options, string eventType, string details)
{
if (!options.EnableLogging)
return;
try
{
// Get the current user session
var sessionManager = new UserSessionManager();
var currentUser = sessionManager.GetCurrentInteractiveUser();
string userInfo = currentUser != null ? $"{currentUser.DomainName}\\{currentUser.UserName} (Session: {currentUser.SessionId})" : "Unknown user";
// Create the log entry
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{eventType}] ID: {options.Id}, Title: {options.Title}, User: {userInfo}, Details: {details}";
// Log to the specified action
if (options.LogAction != null)
{
options.LogAction(logEntry);
}
else
{
// Default logging to debug output
System.Diagnostics.Debug.WriteLine(logEntry);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error logging notification event: {ex.Message}");
}
}
/// <summary>
/// Schedules a reminder for a deferred notification
/// </summary>
/// <param name="options">The notification options</param>
/// <param name="deferOption">The selected deferral option</param>
private void ScheduleDeferralReminder(NotificationOptions options, DeferralOption deferOption)
{
try
{
// Calculate the reminder time
DateTime reminderTime = DateTime.Now.Add(deferOption.DeferralTime);
// Create a task scheduler task
using (TaskService ts = new TaskService())
{
// Create a new task definition
TaskDefinition td = ts.NewTask();
td.RegistrationInfo.Description = $"Reminder for notification: {options.Title}";
// Create a trigger that will fire at the reminder time
td.Triggers.Add(new TimeTrigger(reminderTime));
// Create an action to show the reminder notification
string actionPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
string actionArgs = $"-ExecutionPolicy Bypass -Command \"& {{ $bytes = [System.Convert]::FromBase64String('{options.ReminderData}'); $assembly = [System.Reflection.Assembly]::Load($bytes); $notificationManager = New-Object WindowsNotifications.NotificationManager; $options = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::Deserialize([System.IO.MemoryStream]::new($bytes)); $notificationManager.ShowNotification($options); }}\"";
td.Actions.Add(new ExecAction(actionPath, actionArgs, null));
// Register the task
ts.RootFolder.RegisterTaskDefinition($"WindowsNotification_Reminder_{options.Id}", td);
}
// Log the scheduled reminder
LogNotificationEvent(options, "ReminderScheduled", $"Reminder scheduled for {reminderTime}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error scheduling deferral reminder: {ex.Message}");
}
}
/// <summary>
/// Registers the COM server for notifications
/// </summary>
private void RegisterComServer()
{
try
{
// Register the AppID for the current process
string appUserModelId = APP_ID;
SetCurrentProcessExplicitAppUserModelID(appUserModelId);
}
catch (Exception ex)
{
throw new Exception("Failed to register COM server for notifications", ex);
}
}
[DllImport("shell32.dll", SetLastError = true)]
private static extern int SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
}
}
@@ -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>
+46
View File
@@ -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
}