Notify user when insecure/older OS detected

Create a message for users alerting them about using an insecure operating system.
Version 1.3.26
Created on 2021-05-12
Modified on 2021-10-29
Created by Guy Leech
Downloads: 29

The Script Copy Script Copied to clipboard
#requires -version 3

<#
.SYNOPSIS

Create a read-only WPF window containing a text block showing a message, with or without a title.

.DETAILS

Most attributes of the window such as text colour, size, font can be controlled via parameters

.PARAMETER message

The message to display in the dialogue

.PARAMETER title

The title for the dialogue. If specified as an empty string or $null, no title bar is shown

.PARAMETER fullScreen

The dialogue with cover the entire screen (primary monitor)

.PARAMETER screenPercentage

The percentage of the screen resolution to make the dialogue dimensions

.PARAMETER backgroundColour

The colour for the solid background specified as R,G,B values

.PARAMETER textColour

The colour for the text specified as R,G,B values

.PARAMETER fontSize

The font size to use

.PARAMETER fontFamily

The font family to use

.PARAMETER showForSeconds

Show the dialogue for this number of seconds. The default is to show until dismissed

.PARAMETER noClickToClose

A left mouse click on the dialogue will not close the dialogue

.PARAMETER position

The position on the screen to place the dialogue

.PARAMETER notTopmost

Do not make the dialogue system modal

.PARAMETER noClose

Do not allow the window to be closed. It will only close if -showForSeconds is specified and that number of seconds has been reached

.EXAMPLE

& '.\Message user session.ps1' -message "Please logoff and back on" -title "Message from $env:Username at $(Get-Date -Format G)" -noclose

Display a popup in the bottom right hand corner of the session with the given text and title. This popup cannot be closed.

.EXAMPLE

& '.\Message user session.ps1' -message "Please logoff and back on" -position Center -screenPercentage 50 -showForSeconds 60 -fontSize 48

Display a popup in the center of the screen in the user's session which is 50% of the screen resolution with the given text, in 48 point, but with no title.
This popup can closed by clicking on the X, pressing Alt F4 or left clicking the popup.
The popup will close automatically after 60 seconds.

.CONTEXT

Session (user session)

.NOTES

Must run in the user's session (or ControlUp console) in order for the WPF code to display a dialogue

.MODIFICATION_HISTORY:

@guyrleech  2021-05-10  Initial release

#>

[CmdletBinding()]

Param
(
    ## client metrics (or any parameter passed automagically via CU) must start with an underscore and have the number of the positional parameter in the $message string at the end (which they are sorted on before constructing the message string), eg _clientMetric2
    ## do not have digits anywhere else in the parameter name other than at the end
    ## if not passing any record properties via the SBA definition, delete the _ parameter(s) completely
    ## to show a number without decimal places, make the parameter an [int] type
    $_clientMetric1 ,
    $_clientMetric2 ,
    $_clientMetric3 ,
    $_clientMetric4 ,
    $_clientMetric5 ,
    [string]$message = 'Your WiFi signal is weak' ,
    [AllowEmptyString()][AllowNull()]
    [string]$title , ## if null or empty string then there will be no title
    [switch]$fullScreen ,
    [ValidateScript({$_ -gt 0 -and $_ -le 100})] 
    [int]$screenPercentage = 25 ,
    [string[]]$backgroundColour ,
    [string[]]$textColour ,
    [int]$fontSize ,
    [string]$fontFamily ,
    [int]$showForSeconds ,
    [switch]$noClickToClose , ## left mouse click does not close the dialogue
    [ValidateSet('TopLeft','TopRight','BottomLeft','BottomRight','Centre','Center')]
    [string]$position = 'BottomRight' ,
    [switch]$notTopmost ,
    [switch]$noClose 
)

[string]$notificationWindowXAML = @'
<Window x:Name="wndMain" x:Class="UserNotifier.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:UserNotifier"
        mc:Ignorable="d"
        Title="ControlUp Notification" SizeToContent="WidthAndHeight" ResizeMode="NoResize" Background="#FFE61414">
        <TextBlock x:Name="txtblckMain" Grid.Column="2" HorizontalAlignment="Stretch" Margin="50,50,50,50" TextWrapping="Wrap" VerticalAlignment="Stretch" FontFamily="Raleway ExtraBold" FontWeight="Bold" FontSize="36" TextAlignment="Center"/>
</Window>
'@

$VerbosePreference = $(if( $PSBoundParameters[ 'verbose' ] ) { $VerbosePreference } else { 'SilentlyContinue' })
$DebugPreference = $(if( $PSBoundParameters[ 'debug' ] ) { $DebugPreference } else { 'SilentlyContinue' })
$ErrorActionPreference = $(if( $PSBoundParameters[ 'erroraction' ] ) { $ErrorActionPreference } else { 'Stop' })
$ProgressPreference = 'SilentlyContinue'

[int]$sessionId = Get-Process -Id $pid | Select-Object -ExpandProperty SessionId

if( $sessionId -eq 0 )
{
    Throw "Notifications cannot be shown in session zero - set the script to run in the context of the users session"
}

## get the underscore parameters from the parameters so we can expand the message string - put in hashtable keyed on number at the end of the parameter name so we can sort on that and check for duplicates
[hashtable]$messageStrings = @{}

ForEach( $parameter in $PSBoundParameters.GetEnumerator() )
{
    if( $parameter.Key -match '^_[^\d]*(\d*)$' )  ## _clientMetric1
    {
        try
        {
            $messageStrings.Add( [int]$Matches[1] , $parameter.Value )
        }
        catch
        {
            Throw "Already have an _ parameter ending in number $($Matches[1]) so can't use $($parameter.Key)"
        }
    }
}


Write-Verbose -Message "Got $($messageStrings.Count) parameters for message string"

if( $message -match '\{0\}' -and $messageStrings.Count -eq 0 )
{
    Write-Warning -Message "Message string contains {0} but no record properties were passed as parameters"
}

$message = $message -f ($messageStrings.GetEnumerator() | Sort-Object -Property Key | Select-Object -ExpandProperty Value)

#Write-Verbose -Message "Expanded message text is `"$($WPFtxtblckMain.Text)`"" ##Is this used?

Write-Verbose -Message "Session id is $sessionId user name $env:USERNAME"


Function New-Form
{
    Param
    (
        [Parameter(Mandatory=$true)]
        $inputXaml
    )

    $form = $null
    $inputXML = $inputXaml -replace 'mc:Ignorable="d"' , '' -replace 'x:N' ,'N'  -replace '^<Win.*' , '<Window'
 
    [xml]$xaml = $inputXML

    if( $xaml )
    {
        $reader = New-Object -TypeName Xml.XmlNodeReader -ArgumentList $xaml

        try
        {
            $form = [Windows.Markup.XamlReader]::Load( $reader )
        }
        catch
        {
            Throw "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .NET is installed.`n$_"
        }
 
        $xaml.SelectNodes( '//*[@Name]' ) | ForEach-Object `
        {
            Set-Variable -Name "WPF$($_.Name)" -Value $Form.FindName($_.Name) -Scope Global
        }
    }
    else
    {
        Throw "Failed to convert input XAML to WPF XML"
    }

    $form
}

## don't make mandatory so that can be set in the param() block
if( [string]::IsNullOrEmpty( $message ) )
{
    Throw 'Must specify message text to display'
}

if( $PSBoundParameters[ 'fullScreen' ] )
{
    if( $PSBoundParameters[ 'percentage' ] -and $percentage -ne 100 )
    {
        Throw 'Must not specify -percentage when not 100% if -fullscreen also specified'
    }
    $screenPercentage = 100
}

Add-Type -AssemblyName PresentationCore,PresentationFramework,WindowsBase,System.Windows.Forms

if( ! ( $mainForm = New-Form -inputXaml $notificationWindowXAML ) )
{
    Exit 1
}

if( $null -ne $backgroundColour -and $backgroundColour.Count -eq 1 -and $backgroundColour[0].IndexOf( ',' ) -ge 0 )
{
    $backgroundColour = $backgroundColour -split ','
}

if( $null -ne $textColour -and $textColour.Count -eq 1 -and $textColour[0].IndexOf( ',' ) -ge 0 )
{
    $textColour = $textColour -split ','
}

[int]$appliedDPI = Get-ItemProperty -Path 'HKCU:\Control Panel\Desktop\WindowMetrics' -Name 'AppliedDPI' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'AppliedDPI' -ErrorAction SilentlyContinue
[double]$appliedDPIScaling = $appliedDPI / 96 ## 100% DPI scaling is 96dpi

Write-Verbose -Message "Applied DPI scaling is $appliedDPIScaling"

[double]$scaleFactor = 1
switch( $appliedDPI )
{
    96  { $scaleFactor = 1 }
    144 { $scaleFactor = 2 }
}

Write-Verbose -Message "Applied DPI scaling is $appliedDPIScaling, scale factor $scaleFactor"

$WPFtxtblckMain.Text = "$message"

$mainForm.WindowStyle = 'ToolWindow'

if( [string]::IsNullOrEmpty( $title ) )
{
    $mainForm.WindowStyle = 'None' ## no title bar/control - have to close with Alt F4 if -noClickToClose specified
}
else
{
    $mainForm.Title = $title
}

$mainForm.Topmost = ! $notTopmost

$primaryScreen = [System.Windows.Forms.Screen]::PrimaryScreen

Write-Verbose -Message "Primary screen $($primaryScreen.WorkingArea.Width) x $($primaryScreen.WorkingArea.Height), working area $($primaryScreen.Bounds.Width) x $($primaryScreen.Bounds.Height)"

$WPFtxtblckMain.Width  = ( $primaryScreen.WorkingArea.Width * $screenPercentage / 100 ) 
$WPFtxtblckMain.Height = ( $primaryScreen.WorkingArea.Height * $screenPercentage / 100 ) 

## can't get/set size of containing window so figure out it's size from textblock and margins
[int]$width  = $WPFtxtblckMain.Width + $WPFtxtblckMain.Margin.Left + $WPFtxtblckMain.Margin.Right
[int]$height = $WPFtxtblckMain.Height + $WPFtxtblckMain.Margin.Top + $WPFtxtblckMain.Margin.Bottom

if( ! [string]::IsNullOrEmpty( $title ) )
{
    ## adjust for title bar and borders
    $height += [System.Windows.SystemParameters]::CaptionHeight  + [System.Windows.SystemParameters]::BorderWidth * 2
    $width +=  [System.Windows.SystemParameters]::BorderWidth * 2 ## left and right borders
}

if( $fullScreen ) ## don't adjust for margins if fullscreen
{
    $mainForm.Left = $mainForm.Top = 0
}
else
{
    switch -Regex ( $position )
    {
        'TopLeft'
        {
            $mainForm.Left = 0
            $mainForm.Top = 0
        }
        'TopRight'
        {
            $mainForm.Left = $primaryScreen.WorkingArea.Width - $Width
            $mainForm.Top = 0
        }
        'BottomLeft'
        {
            $mainForm.Left = 0
            $mainForm.Top = $primaryScreen.WorkingArea.Height - $Height
        }
        'BottomRight'
        {
            $mainForm.Left = $primaryScreen.WorkingArea.Width  - $width
            $mainForm.Top  = $primaryScreen.WorkingArea.Height - $height
        }
        'Centre|Center'
        {
            $mainForm.Left = ( $primaryScreen.WorkingArea.Width  - $Width  ) / 2
            $mainForm.Top  = ( $primaryScreen.WorkingArea.Height - $Height ) / 2
        }
    }
}

if( $PSBoundParameters[ 'backgroundColour' ] )
{
    if( $backgroundColour.Count -ne 3 )
    {
        Throw "Must specify R,G,B values for background colour"
    }
    $colour = [System.Windows.Media.Color]::FromRgb( $backgroundColour[0] , $backgroundColour[1] , $backgroundColour[2] )
    $mainForm.Background = [System.Windows.Media.SolidColorBrush]::new( $colour )
}

if( $PSBoundParameters[ 'textColour' ] )
{
    if( $textColour.Count -ne 3 )
    {
        Throw "Must specify R,G,B values for text colour"
    }
    $colour = [System.Windows.Media.Color]::FromRgb( $textColour[0] , $textColour[1] , $textColour[2] )
    $WPFtxtblckMain.Foreground = [System.Windows.Media.SolidColorBrush]::new( $colour )
}

if( $PSBoundParameters[ 'fontSize' ] )
{
    $WPFtxtblckMain.FontSize = $fontSize
}

if( $PSBoundParameters[ 'fontFamily' ] )
{
    $WPFtxtblckMain.FontFamily = $fontFamily
}

$script:formTimer = $null
[bool]$script:timerExpired = $false

[scriptblock]$timerBlock = `
{
    $this.Stop()
    $script:timerExpired = $true
    $mainForm.Close()
}

if( $PSBoundParameters.ContainsKey( 'showForSeconds' ) -and $showForSeconds -gt 0 )
{
    ## https://richardspowershellblog.wordpress.com/2011/07/07/a-powershell-clock/
    $mainForm.Add_SourceInitialized({
        if( $script:formTimer = New-Object -TypeName System.Windows.Threading.DispatcherTimer )
        {
            $script:formTimer.Interval = New-TimeSpan -Seconds $showForSeconds
            $script:formTimer.Add_Tick( $timerBlock )
            $script:formTimer.Start()
        }
    })
}

## allow left button to close the form unless specified not to or the form itselt can't be closed
if( ! $noClickToClose -and ! $noClose )
{
    $mainForm.add_MouseLeftButtonUp({
        $mainForm.Close()
    })
}

if( $noClose )
{
    $mainForm.add_Closing({
        $_.Cancel = ! $script:timerExpired ## do not allow close unless the timer has expired otherwise the timer can't close the window
    })
}

Write-Verbose -Message "Dialog $($Width) x $($Height) at $($mainForm.Left) , $($mainForm.Top)"

$mainForm.Left /= $scaleFactor 
$mainForm.Top  /= $scaleFactor

Write-Verbose -Message "After scaling: dialog $($Width) x $($Height) at $($mainForm.Left) , $($mainForm.Top)"

$mainForm.UpdateLayout()
$result = $mainForm.ShowDialog()

Write-Verbose -Message "Dialog after is at $($mainForm.Left) , $($mainForm.Top)"

if( $script:formTimer )
{
    $script:formTimer.Stop()
    $script:formTimer.remove_Tick( $timerBlock )
    $script:formTimer = $null
    $timerBlock = $null
    Remove-Variable -Name timerBlock -Force -Confirm:$false
}