PDQ Sticky notification - Systray and MSG box notificaitons for install complete for users to close

<# .SYNOPSIS Displays a notification using a tray balloon tip and/or a styled modal form. .DESCRIPTION Designed for PDQ Deploy using "Run As Logged On User". Can display tray and/or modal messages. Supports non-blocking form display via -nowait, or blocking interaction with OK/Cancel/Timeout return codes. .PARAMETER msg Required. Message to display. .PARAMETER title Optional. Title of the modal or balloon (default: "Message"). .PARAMETER minutes Optional. Tray icon visibility time in minutes (default: -1 = indefinite). .PARAMETER timeout Optional. Auto-close modal after X seconds. Returns 2 on timeout. .PARAMETER NoSystray Optional. Suppress tray balloon. .PARAMETER NoForm Optional. Suppress modal form. .PARAMETER NoImage Optional. Hide image from modal. .PARAMETER Cancel Optional. Show Cancel button. Returns -1 on cancel/[X]. .PARAMETER image Optional. Override image path. .PARAMETER nowait Optional. Run modal in background process. No return value to caller. .PARAMETER help Optional. Show help message. .EXAMPLES Notify-Sticky.ps1 -msg "Complete" -timeout 10 Notify-Sticky.ps1 -msg "Done" -nowait Notify-Sticky.ps1 -msg "Notice" -NoForm #> # Define parameters with default values param ( [string]$msg, # Message to display (required) [string]$title = "Message", # Modal/tray title (default: Message) [int]$minutes = -1, # Tray lifetime in minutes (-1 = indefinite) [int]$timeout = 0, # Modal auto-close timeout in seconds [switch]$NoSystray, # Suppress tray balloon [switch]$NoForm, # Suppress modal form [switch]$NoImage, # Suppress image in modal [switch]$Cancel, # Show Cancel button [string]$image, # Override image path [switch]$nowait, # Launch modal in background [switch]$help # Show help text and exit ) # Set default fallback image path $DEFAULT_IMAGE_PATH = "\\gal-arc01\T\Logos-Icons\Galloway\Galloway Avatar Circle_CMYK.png" # Show help content function function Show-Help { @" Notify-Sticky.ps1 -msg "text" [options] -title "text" Optional title (default: Message) -minutes X Tray time (default: -1 = forever) -timeout X Auto-close modal (in seconds) (returns 2) -nowait Launch modal in background, don't wait -NoSystray Skip tray balloon -NoForm Skip modal window -Cancel Show Cancel button (returns -1) -NoImage Suppress image in modal -image path.png Use alternate image path -help Show help and exit "@ | Write-Output exit 1 } # Handle help or missing message if ($help -or !$PSBoundParameters.Count -or -not $msg) { Show-Help } # Prevent empty output by suppressing both visual types if ($NoForm -and $NoSystray) { Write-Error "Nothing to show: both -NoForm and -NoSystray set."; exit 1 } # Load necessary Windows libraries Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing # Function to build and show custom modal dialog function Show-CustomForm { param ( [string]$title, [string]$msg, [string]$imgPath, [switch]$NoImage, [switch]$Cancel, [int]$timeout ) $timedOut = $false # Flag to indicate timeout # Initialize modal window $form = New-Object Windows.Forms.Form $form.Text = $title $form.Width = 600 $form.Height = 220 $form.StartPosition = "CenterScreen" $form.TopMost = $true $form.BackColor = [System.Drawing.Color]::WhiteSmoke $form.FormBorderStyle = 'FixedDialog' # Optional image rendering if (-not $NoImage) { $iconBox = New-Object Windows.Forms.PictureBox $iconBox.SizeMode = 'Zoom' $iconBox.Width = 48 $iconBox.Height = 48 $iconBox.Left = 10 $form.Controls.Add($iconBox) } # Message label control $label = New-Object Windows.Forms.Label $label.Text = $msg $label.Font = New-Object Drawing.Font("Segoe UI", 12) $label.AutoSize = $true $label.MaximumSize = New-Object Drawing.Size(500, 0) $label.Left = if (-not $NoImage) { 70 } else { 20 } $form.Controls.Add($label) # Position UI controls on form shown $form.Add_Shown({ $label.Top = [Math]::Max(10, ($form.ClientSize.Height - $label.Height) / 2 - 20) if (-not $NoImage -and $iconBox -ne $null) { $iconBox.Top = [Math]::Max(10, ($form.ClientSize.Height - $iconBox.Height) / 2 - 20) } $buttonTop = $form.ClientSize.Height - 50 $okBtn.Top = $buttonTop if ($Cancel) { $cancelBtn.Top = $buttonTop } }) # OK button $okBtn = New-Object Windows.Forms.Button $okBtn.Text = "OK" $okBtn.DialogResult = [System.Windows.Forms.DialogResult]::OK $okBtn.Width = 90 $okBtn.Height = 30 $okBtn.Left = if ($Cancel) { 140 } else { ($form.ClientSize.Width - $okBtn.Width) / 2 } $form.AcceptButton = $okBtn $form.Controls.Add($okBtn) # Optional Cancel button if ($Cancel) { $cancelBtn = New-Object Windows.Forms.Button $cancelBtn.Text = "Cancel" $cancelBtn.DialogResult = [System.Windows.Forms.DialogResult]::Cancel $cancelBtn.Width = 90 $cancelBtn.Height = 30 $cancelBtn.Left = 270 $form.CancelButton = $cancelBtn $form.Controls.Add($cancelBtn) } # Auto-close logic if ($timeout -gt 0) { $timer = New-Object Windows.Forms.Timer $timer.Interval = $timeout * 1000 $timer.Add_Tick({ $timedOut = $true $form.DialogResult = [Windows.Forms.DialogResult]::None $form.Close() }) $timer.Start() } # Attempt image loading if (-not $NoImage -and $iconBox -ne $null -and (Test-Path $imgPath)) { try { $iconImage = [System.Drawing.Image]::FromFile($imgPath) $iconBox.Image = $iconImage } catch {} } $result = $form.ShowDialog() return @{ Result = $result; TimedOut = $timedOut } } # Show system tray balloon if not suppressed if (-not $NoSystray) { $tray = New-Object System.Windows.Forms.NotifyIcon $tray.Icon = [System.Drawing.SystemIcons]::Information $tray.Text = $title $tray.Visible = $true $tray.ShowBalloonTip(5000, $title, $msg, [System.Windows.Forms.ToolTipIcon]::Info) } # Main modal logic if (-not $NoForm) { $imgPath = if ($image) { $image } else { $DEFAULT_IMAGE_PATH } if ($nowait) { # Prepare script block as background file $escapedTitle = $title.Replace('"', '""') $escapedMsg = $msg.Replace('"', '""') $escapedImg = $imgPath.Replace('"', '""') $scriptContent = @" Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing function Show-CustomForm { $(Get-Command Show-CustomForm | Select-Object -ExpandProperty ScriptBlock) } \$r = Show-CustomForm -title "$escapedTitle" -msg "$escapedMsg" -imgPath "$escapedImg" -NoImage:\$$($NoImage.IsPresent) -Cancel:\$$($Cancel.IsPresent) -timeout $timeout if (\$r.TimedOut) { exit 2 } elseif (\$r.Result -ne [Windows.Forms.DialogResult]::OK) { exit -1 } else { exit 0 } "@ $tempFile = [System.IO.Path]::GetTempFileName().Replace(".tmp", ".ps1") [System.IO.File]::WriteAllText($tempFile, $scriptContent, [System.Text.Encoding]::Unicode) # Launch form as background process Start-Process powershell.exe -ArgumentList "-NoProfile", "-ExecutionPolicy", "Bypass", "-STA", "-File", "$tempFile" exit 0 } else { $r = Show-CustomForm -title $title -msg $msg -imgPath $imgPath -NoImage:$NoImage -Cancel:$Cancel -timeout $timeout if ($r.TimedOut) { exit 2 } elseif ($r.Result -ne [System.Windows.Forms.DialogResult]::OK) { exit -1 } else { exit 0 } } } # Tray-only wait logic if ($NoForm -and -not $NoSystray) { if ($minutes -eq -1) { while ($true) { Start-Sleep -Seconds 1 } # Run indefinitely } else { Start-Sleep -Seconds ($minutes * 60) # Wait specified minutes } $tray.Visible = $false $tray.Dispose() exit 0 }

Comments

Popular posts from this blog

Revit CSV file manager for families and re-exporting to a CSV file

Revit area plans adding new types and references (Gross and rentable)