Last active
August 18, 2025 07:38
-
-
Save ChrisColeTech/1f79919c60bf210982a04bc607a1a1d7 to your computer and use it in GitHub Desktop.
Windows Toast Notification Installer - Native Windows notifications (WORKING)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Windows Toast Notification Installer | |
| # This script sets up toast notifications for Windows using the BurntToast module | |
| # Usage: Run this script in PowerShell as Administrator (recommended) or current user | |
| param( | |
| [switch]$Unattended = $false | |
| ) | |
| # Function to write colored output | |
| function Write-ColorOutput($ForegroundColor) { | |
| $fc = $host.UI.RawUI.ForegroundColor | |
| $host.UI.RawUI.ForegroundColor = $ForegroundColor | |
| if ($args) { | |
| Write-Output $args | |
| } else { | |
| $input | Write-Output | |
| } | |
| $host.UI.RawUI.ForegroundColor = $fc | |
| } | |
| Write-ColorOutput Green "=== Windows Toast Notification Installer ===" | |
| Write-Output "" | |
| # Cleanup old installations | |
| Write-Output "Checking for old installations to clean up..." | |
| # Clean up any user-specific PATH entries that contain toast | |
| $currentUserPath = [Environment]::GetEnvironmentVariable("PATH", "User") | |
| if ($currentUserPath) { | |
| $pathParts = $currentUserPath -split ';' | |
| $cleanedParts = @() | |
| $removedPaths = @() | |
| foreach ($pathPart in $pathParts) { | |
| if ($pathPart) { | |
| # Check if this path contains old toast installations | |
| $checkPath = $pathPart.TrimEnd('\') | |
| if ((Test-Path "$checkPath\toast.ps1") -or | |
| (Test-Path "$checkPath\toast.cmd") -or | |
| (Test-Path "$checkPath\toast")) { | |
| # Skip paths that aren't our target ProgramData location | |
| if ($checkPath -ne "C:\ProgramData\Toast") { | |
| $removedPaths += $checkPath | |
| Write-Output "Removing old toast files from: $checkPath" | |
| Remove-Item -Path "$checkPath\toast.ps1" -Force -ErrorAction SilentlyContinue | |
| Remove-Item -Path "$checkPath\toast.cmd" -Force -ErrorAction SilentlyContinue | |
| Remove-Item -Path "$checkPath\toast" -Force -ErrorAction SilentlyContinue | |
| continue | |
| } | |
| } | |
| $cleanedParts += $pathPart | |
| } | |
| } | |
| if ($removedPaths.Count -gt 0) { | |
| Write-Output "Removed toast installations from PATH: $($removedPaths -join ', ')" | |
| $cleanedPath = $cleanedParts -join ';' | |
| [Environment]::SetEnvironmentVariable("PATH", $cleanedPath, "User") | |
| $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + $cleanedPath | |
| } | |
| } | |
| # Clean up bash profile toast functions if they exist | |
| $bashProfiles = @( | |
| "$env:USERPROFILE\.bashrc", | |
| "$env:USERPROFILE\.bash_profile", | |
| "$env:USERPROFILE\.profile" | |
| ) | |
| foreach ($profilePath in $bashProfiles) { | |
| if (Test-Path $profilePath) { | |
| $content = Get-Content $profilePath -Raw | |
| if ($content -like "*toast*helper*" -or $content -like "*toast()*") { | |
| Write-Output "Cleaning up old bash toast functions from: $profilePath" | |
| # Remove toast helper blocks | |
| $cleaned = $content -replace '(?s)# -- .*?toast.*?helper start --.*?# -- .*?toast.*?helper end --\r?\n?', '' | |
| # Remove standalone toast functions | |
| $cleaned = $cleaned -replace '(?s)toast\(\)\s*\{[^}]*\}\r?\n?', '' | |
| if ($cleaned -ne $content) { | |
| Set-Content -Path $profilePath -Value $cleaned -Encoding UTF8 -NoNewline | |
| } | |
| } | |
| } | |
| } | |
| Write-Output "Cleanup complete." | |
| Write-Output "" | |
| # Create global toast directory in ProgramData | |
| $binDir = "C:\ProgramData\Toast" | |
| if (-not (Test-Path $binDir)) { | |
| Write-Output "Creating global toast directory: $binDir" | |
| New-Item -ItemType Directory -Path $binDir -Force | Out-Null | |
| } | |
| # Download the icon image | |
| $iconPath = Join-Path $binDir "app-icon.png" | |
| if (-not (Test-Path $iconPath)) { | |
| Write-Output "Downloading toast notification icon..." | |
| try { | |
| $imageUrl = 'https://github.com/ChrisColeTech/claude-wrapper/raw/main/app-1.png' | |
| Invoke-WebRequest -Uri $imageUrl -OutFile $iconPath | |
| Write-Output "Icon downloaded successfully" | |
| } catch { | |
| Write-Warning "Failed to download icon: $_" | |
| $iconPath = $null | |
| } | |
| } else { | |
| Write-Output "Icon already exists" | |
| } | |
| # Create toast.ps1 script | |
| $toastPs1Path = Join-Path $binDir "toast.ps1" | |
| $toastPs1Content = @" | |
| Param( | |
| [Parameter(Position=0)] [string]`$Title = "Notification", | |
| [Parameter(Position=1)] [string]`$Body = "" | |
| ) | |
| # Ensure module available per-user (non-fatal if blocked) | |
| if (-not (Get-Module -ListAvailable -Name BurntToast)) { | |
| try { | |
| Write-Host "Installing BurntToast module..." | |
| Install-Module -Name BurntToast -Scope CurrentUser -Force -ErrorAction Stop | |
| } catch { | |
| Write-Warning "Failed to install BurntToast module: `$_" | |
| exit 1 | |
| } | |
| } | |
| Import-Module BurntToast -ErrorAction SilentlyContinue | |
| if (-not (Get-Module BurntToast)) { | |
| Write-Error "BurntToast module not available" | |
| exit 1 | |
| } | |
| `$text = @(`$Title) | |
| if (`$Body -ne '') { `$text += `$Body } | |
| try { | |
| # Use custom icon if available | |
| `$iconPath = "C:\ProgramData\Toast\app-icon.png" | |
| if (Test-Path `$iconPath) { | |
| `$logo = New-BTImage -Source `$iconPath | |
| New-BurntToastNotification -Text `$text -AppLogo `$logo | |
| } else { | |
| New-BurntToastNotification -Text `$text | |
| } | |
| } catch { | |
| Write-Error "Failed to create toast notification: `$_" | |
| exit 1 | |
| } | |
| "@ | |
| Write-Output "Creating PowerShell script: $toastPs1Path" | |
| Set-Content -Path $toastPs1Path -Value $toastPs1Content -Encoding UTF8 | |
| # Create toast.cmd wrapper with full PowerShell path | |
| $toastCmdPath = Join-Path $binDir "toast.cmd" | |
| $toastCmdContent = @' | |
| @echo off | |
| "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Bypass -File "C:\ProgramData\Toast\toast.ps1" %* | |
| '@ | |
| Write-Output "Creating CMD wrapper: $toastCmdPath" | |
| Set-Content -Path $toastCmdPath -Value $toastCmdContent -Encoding ASCII | |
| # Add bin directory to PATH if not already present | |
| $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") | |
| if ($currentPath -notlike "*$binDir*") { | |
| Write-Output "Adding $binDir to user PATH..." | |
| $newPath = "$currentPath;$binDir" | |
| [Environment]::SetEnvironmentVariable("PATH", $newPath, "User") | |
| $env:PATH = "$env:PATH;$binDir" | |
| } else { | |
| Write-Output "Bin directory already in PATH" | |
| } | |
| # Update PowerShell profile | |
| $profilePath = $PROFILE.CurrentUserAllHosts | |
| $profileDir = Split-Path $profilePath -Parent | |
| if (-not (Test-Path $profileDir)) { | |
| Write-Output "Creating PowerShell profile directory: $profileDir" | |
| New-Item -ItemType Directory -Path $profileDir -Force | Out-Null | |
| } | |
| $profileSnippet = @' | |
| # Toast notification functions | |
| if (-not (Get-Command toast -ErrorAction SilentlyContinue)) { | |
| function Show-Toast { | |
| param([string]$Title = "Notification", [string]$Body = "") | |
| & "C:\ProgramData\Toast\toast.ps1" $Title $Body | |
| } | |
| Set-Alias -Name toast -Value Show-Toast | |
| Set-Alias -Name t -Value Show-Toast | |
| } | |
| '@ | |
| $profileExists = Test-Path $profilePath | |
| if ($profileExists) { | |
| Write-Output "Checking for existing toast functions in PowerShell profile..." | |
| $profileContent = Get-Content $profilePath -Raw | |
| # Remove any existing toast function blocks (clean update) | |
| if ($profileContent -like "*Toast notification functions*") { | |
| Write-Output "Removing old toast functions from PowerShell profile..." | |
| # Use regex to remove the entire toast function block | |
| $pattern = '(?s)# Toast notification functions.*?(?=\r?\n\r?\n(?![\s]*[}]|[\s]*Set-Alias|[\s]*function)|\z)' | |
| $cleanedContent = $profileContent -replace $pattern, '' | |
| # Clean up any extra blank lines | |
| $cleanedContent = $cleanedContent -replace '\r?\n\r?\n\r?\n+', "`r`n`r`n" | |
| # Write the cleaned content back to profile | |
| Set-Content -Path $profilePath -Value $cleanedContent -Encoding UTF8 -NoNewline | |
| } | |
| } | |
| Write-Output "Adding updated toast functions to PowerShell profile: $profilePath" | |
| Add-Content -Path $profilePath -Value $profileSnippet -Encoding UTF8 | |
| # Test installation | |
| Write-Output "" | |
| Write-ColorOutput Green "Installation complete!" | |
| Write-Output "" | |
| Write-Output "Testing installation..." | |
| try { | |
| & $toastPs1Path "Installation Complete" "Windows toast notifications with custom icon are now available" | |
| Write-ColorOutput Green "[SUCCESS] Test notification sent successfully!" | |
| } catch { | |
| Write-ColorOutput Red "[ERROR] Test notification failed: $_" | |
| } | |
| # Configure Claude Code hooks | |
| Write-Output "" | |
| Write-ColorOutput Green "Configuring Claude Code hooks..." | |
| $claudeSettingsDir = "$env:USERPROFILE\.claude" | |
| $claudeSettingsPath = "$claudeSettingsDir\settings.json" | |
| # Important warning about hooks replacement | |
| Write-ColorOutput Yellow "IMPORTANT: This installer will replace any existing Claude Code hooks" | |
| Write-ColorOutput Yellow "with toast notification hooks for optimal functionality." | |
| Write-Output "" | |
| if (-not (Test-Path $claudeSettingsDir)) { | |
| Write-Output "Creating Claude settings directory: $claudeSettingsDir" | |
| New-Item -ItemType Directory -Path $claudeSettingsDir -Force | Out-Null | |
| } | |
| $criticalHooks = @{ | |
| SessionStart = @( | |
| @{ | |
| matcher = "startup" | |
| hooks = @( | |
| @{ | |
| type = "command" | |
| command = 'toast "Claude Code" "New session started - Ready to help!"' | |
| } | |
| ) | |
| }, | |
| @{ | |
| matcher = "resume" | |
| hooks = @( | |
| @{ | |
| type = "command" | |
| command = 'toast "Claude Code" "Session resumed - Continuing where we left off"' | |
| } | |
| ) | |
| } | |
| ) | |
| Notification = @( | |
| @{ | |
| hooks = @( | |
| @{ | |
| type = "command" | |
| command = 'toast "Claude Code" "User input needed to continue"' | |
| } | |
| ) | |
| } | |
| ) | |
| Stop = @( | |
| @{ | |
| hooks = @( | |
| @{ | |
| type = "command" | |
| command = 'toast "Claude Code" "Task completed successfully"' | |
| } | |
| ) | |
| } | |
| ) | |
| SubagentStop = @( | |
| @{ | |
| hooks = @( | |
| @{ | |
| type = "command" | |
| command = 'toast "Claude Code" "Subagent task finished"' | |
| } | |
| ) | |
| } | |
| ) | |
| PreCompact = @( | |
| @{ | |
| hooks = @( | |
| @{ | |
| type = "command" | |
| command = 'toast "Claude Code" "Compacting context to continue"' | |
| } | |
| ) | |
| } | |
| ) | |
| } | |
| $settings = @{} | |
| if (Test-Path $claudeSettingsPath) { | |
| Write-Output "Found existing Claude Code settings file" | |
| # Create timestamped backup | |
| $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" | |
| $backupPath = "$claudeSettingsPath.backup_$timestamp" | |
| try { | |
| Copy-Item $claudeSettingsPath $backupPath -Force | |
| Write-ColorOutput Green "Created backup: $backupPath" | |
| } catch { | |
| Write-Warning "Failed to create backup: $_" | |
| } | |
| # Read existing settings | |
| try { | |
| $existingJson = Get-Content $claudeSettingsPath -Raw | ConvertFrom-Json | |
| # Convert PSCustomObject to hashtable while preserving all properties | |
| $settings = @{} | |
| $existingJson.PSObject.Properties | ForEach-Object { | |
| $settings[$_.Name] = $_.Value | |
| } | |
| Write-Output "Preserved existing settings: $($settings.Keys -join ', ')" | |
| } catch { | |
| Write-Warning "Could not parse existing settings, creating new configuration" | |
| $settings = @{} | |
| } | |
| } else { | |
| Write-Output "Creating new Claude settings file..." | |
| } | |
| # Create clean JSON manually to ensure correct format | |
| Write-Output "Updating Claude settings with critical hooks..." | |
| # Build settings preserving existing configuration | |
| $finalSettings = @{} | |
| foreach ($key in $settings.Keys) { | |
| if ($key -ne "hooks") { | |
| $finalSettings[$key] = $settings[$key] | |
| } | |
| } | |
| # Build settings preserving existing configuration and create clean JSON manually | |
| $existingSettingsJson = "" | |
| foreach ($key in $finalSettings.Keys) { | |
| if ($key -ne "hooks") { | |
| $value = $finalSettings[$key] | |
| if ($value -is [string]) { | |
| $existingSettingsJson += " `"$key`": `"$value`",`n" | |
| } else { | |
| $existingSettingsJson += " `"$key`": $($value | ConvertTo-Json -Compress),`n" | |
| } | |
| } | |
| } | |
| # Create the JSON manually to avoid encoding issues with emojis | |
| $hooksJsonContent = @" | |
| { | |
| $existingSettingsJson "hooks": { | |
| "SessionStart": [ | |
| { | |
| "matcher": "startup", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "toast \"Claude Code\" \"New session started - Ready to help!\"" | |
| } | |
| ] | |
| }, | |
| { | |
| "matcher": "resume", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "toast \"Claude Code\" \"Session resumed - Continuing where we left off\"" | |
| } | |
| ] | |
| } | |
| ], | |
| "Notification": [ | |
| { | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "toast \"Claude Code\" \"User input needed to continue\"" | |
| } | |
| ] | |
| } | |
| ], | |
| "Stop": [ | |
| { | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "toast \"Claude Code\" \"Task completed successfully\"" | |
| } | |
| ] | |
| } | |
| ], | |
| "SubagentStop": [ | |
| { | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "toast \"Claude Code\" \"Subagent task finished\"" | |
| } | |
| ] | |
| } | |
| ], | |
| "PreCompact": [ | |
| { | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "toast \"Claude Code\" \"Compacting context to continue\"" | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } | |
| "@ | |
| # Write with proper UTF-8 encoding | |
| $utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False | |
| [System.IO.File]::WriteAllText($claudeSettingsPath, $hooksJsonContent, $utf8NoBomEncoding) | |
| Write-ColorOutput Green "Critical hooks configured successfully!" | |
| Write-Output "" | |
| Write-Output "Usage examples:" | |
| Write-Output ' toast "Hello" "World"' | |
| Write-Output ' t "Quick notification"' | |
| Write-Output "" | |
| Write-Output "Note: You may need to restart your terminal or run 'refreshenv' to use the toast command." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment