A Customisable WPF MessageBox for PowerShell

Update 2017-08-25 | Changed the way the output from a button click is handled, saving it to a variable instead.

The humble VBScript message box has been around for a while now and is still a useful way of providing a simple pop-up notification in Windows. I’ve used it in my PowerShell scripts as well as my UI tools and even in my ConfigMgr task sequences for providing feedback to the end user.

WscriptMsgBox

Both WinForms and WPF also have a message box but they have similar style and functionality to the VBScript version.  In this age of modern design languages like Metro and Material Design however, this message box is starting to look a little dated. So I decided to create my own customizable message box in WPF as a PowerShell function that can be used in my scripts and UI tools.

Enter New-WPFMessageBox. My goal was to create a good-looking, fully-functional message box window that is highly customizable in the WPF format, and can even be used as a window host to display custom WPF content. The message box has a basic default design, but if you’re the creative type then you can customize away until you get an appearance you like.

Examples

Let’s have a look at some ways we can use this function.

To create a simple message box, all you need to do is pass a string to the Content parameter:


New-WPFMessageBox -Content "I'm a WPF Object!"

img2

The Content parameter is the only mandatory parameter, and can take either a simple text string or your own custom WPF content (which we’ll look at later).

To add a header, simply use the Title parameter:


New-WPFMessageBox -Content "I'm a WPF Object!" -Title "Custom Message Box"

img1

The title bar is a color block, so it can be used to create a nice contrast with the main content, for example. Use the TitleBackground parameter to change the color. This parameter is a dynamic parameter and will allow you to select from the list of available colors in the .Net palette.

img3

 

New-WPFMessageBox -Content "I'm a WPF Object!" -Title "Custom Message Box" -TitleBackground CornflowerBlue

img4

You can change the fonts, font weights and font sizes, as well as the background and foreground colors to create some nice combinations. The FontFamily parameter is also dynamic and allows you to select from fonts available in .Net.

The Content… parameters allow you to change the main content section, and the Title… parameters allow you to change the title.

When using several parameters, it can be easier to ‘splat’ them like so:


$Params = @{
    Content = "I'm a WPF Object!"
    Title = "Custom Message Box"
    TitleBackground = "CornflowerBlue"
    TitleFontSize = 28
    TitleFontWeight = "Bold"
    TitleTextForeground = "SpringGreen"
    ContentFontSize = 18
    ContentFontWeight = "Medium"
    ContentTextForeground = "SpringGreen"
}

New-WPFMessageBox @Params

img5

Use ContentBackground to change the main content background:

img6

You can change the button color using ButtonTextForeground:

img7

The default button is the OK button, but you can also use other buttons or button combinations such as Yes/No, Retry/Cancel, Abort/Retry/Ignore etc. Simply select an option using the ButtonType parameter.


$Params = @{
    Content = "The file 'Important Document.docx' could not be deleted."
    Title = "File Deletion Error"
    TitleBackground = "LightGray"
    TitleFontSize = 28
    TitleFontWeight = "Bold"
    TitleTextForeground = "Red"
    ContentFontSize = 18
    ContentFontWeight = "Medium"
}

New-WPFMessageBox @Params -ButtonType Abort-Retry-Ignore

img8

When a button is clicked, the text of the button is saved to a variable named WPFMessageBoxOutput so you can handle this in your code and perform different actions depending on what was clicked, eg:


New-WPFMessageBox @Params -ButtonType Abort-Retry-Ignore
If ($WPFMessageBoxOutput -eq "Abort")
{
    Exit
}
ElseIf ($WPFMessageBoxOutput -eq "Retry")
{

}
Else
{

}

You can also add your own custom buttons. Set the ButtonType to None, and pass the text for your buttons to the CustomButtons parameter:


$Params = @{
    Content = "The purchase order has been created.  Do you want to go ahead?"
    Title = "Purchase Order"
    TitleFontSize = 20
    TitleBackground = 'Green'
    TitleTextForeground = 'White'
    ButtonType = 'None'
    CustomButtons = "Yes, go ahead!","No, don't do it!", "Save it for later"
}
New-WPFMessageBox @Params

img9

You can create Information, Warning or Error message boxes using your own style. If you define the parameters once in your script, then you can simply call them everytime you want to display that message type.

A simple informational message:


$InfoParams = @{
    Title = "INFORMATION"
    TitleFontSize = 20
    TitleBackground = 'LightSkyBlue'
    TitleTextForeground = 'Black'
}
New-WPFMessageBox @InfoParams -Content "The server has been rebooted at 08:35 on 16th January 2017"

img10

A warning message:


$WarningParams = @{
    Title = "WARNING"
    TitleFontSize = 20
    TitleBackground = 'Orange'
    TitleTextForeground = 'Black'
}
New-WPFMessageBox @WarningParams -Content "The file could not be opened."

img11

An error message:


$ErrorMsgParams = @{
    Title = "ERROR!"
    TitleBackground = "Red"
    TitleTextForeground = "WhiteSmoke"
    TitleFontWeight = "UltraBold"
    TitleFontSize = 20
    Sound = 'Windows Exclamation'
}
New-WPFMessageBox @ErrorMsgParams -Content "There was a problem connecting to the Exchange Server.
Please try again later."

In this one I’ve added the Sound parameter. This is a dynamic parameter allowing you to select from available Windows sounds in your local media library. The sound will be played when the message box is displayed.

img12

Maybe you prefer a little more drama in your error message box. How about this:


$ErrorMsgParams = @{
    TitleBackground = "Red"
    TitleTextForeground = "Yellow"
    TitleFontWeight = "UltraBold"
    TitleFontSize = 28
    ContentBackground = 'Red'
    ContentFontSize = 18
    ContentTextForeground = 'White'
    ButtonTextForeground = 'White'
    Sound = 'Windows Hardware Fail'
}
$ComputerName = "DoesntExist"
Try
{
    New-PSSession -ComputerName $ComputerName -ErrorAction Stop
}
Catch
{
    New-WPFMessageBox @ErrorMsgParams -Content "$_" -Title "PSSession Error!"
}

In this code, I am using a try / catch block to catch an error, then using the message box to display it.

img13

Or we can create a Windows 10 BSOD-style error:


$Params = @{
    FontFamily = 'Verdana'
    Title = ":("
    TitleFontSize = 80
    TitleTextForeground = 'White'
    TitleBackground = 'SteelBlue'
    ButtonType = 'OK'
    ContentFontSize = 16
    ContentTextForeground = 'White'
    ContentBackground = 'SteelBlue'
    ButtonTextForeground = 'White'
    BorderThickness = 0
}
New-WPFMessageBox @Params -Content "The script ran into a problem that it couldn't handle, and now it needs to exit.

0x80050002"

img14

If you prefer square corners and no window shadow effect for a more basic look, try this. Here I have set the CornerRadius, ShadowDepth and BlurRadius to 0, as well as setting the BorderThickness and BorderBrush parameters to create a simple border.


$Params = @{
    Title = "QUESTION"
    TitleBackground ='Navy'
    TitleTextForeground = 'White'
    ButtonType = 'Yes-No'
    CornerRadius = 0
    ShadowDepth = 0
    BlurRadius = 0
    BorderThickness = 1
    BorderBrush = 'Navy'

}
New-WPFMessageBox @Params -Content "The server is not online. Do you wish to proceed?"

img15

You can create some interesting effects with those parameters, such as a more circular window:


$Params = @{
    Title = "QUESTION"
    TitleBackground ='Navy'
    TitleTextForeground = 'White'
    ButtonType = 'Yes-No'
    CornerRadius = 80
    ShadowDepth = 5
    BlurRadius = 5
    BorderThickness = 1
    BorderBrush = 'Navy'

}
New-WPFMessageBox @Params -Content "The server is not online.
Do you wish to proceed?"

img16

Timeout is a useful parameter which allows you to automatically close the message box after a number of seconds. You might want to use this in a script where you choose to continue if no response was received after a period of time. You can also use this to simply display a message without any buttons – just for information, and no response is required.


$Content = "Your mission - which we have chosen to accept for you - is to eliminate Windows XP in the enterprise.
Good luck!

This message will self-destruct in 30 seconds"

$Params = @{
    Content = $Content
    Title = "MISSION:POSSIBLE"
    TitleFontSize = 30
    TitleTextForeground = 'Red'
    TitleFontWeight = 'Bold'
    TitleBackground = 'Silver'
    FontFamily = 'Lucida Console'
    ButtonType = 'None'
    Timeout = 30
}

New-WPFMessageBox @Params

img30

I’ve also added a couple of parameters that allow you to assign code to the Loaded or Closed events on the message box window. This means you can run code when the message box opens, or after it has closed. Simply pass a code block to the OnLoaded or OnClosed parameters. Bear in mind that any code assigned to the Loaded event should not be long running as it will block the thread until it has completed, and the message box will only display afterwards.

In this example, I have used an async method from the SpeechSynthesizer class so that the text will be spoken at the same time the window opens because it is not running in the same thread.


$Content = "We could not access the server. Do you wish to continue?"

$Code = {
    Add-Type -AssemblyName System.speech
    (New-Object System.Speech.Synthesis.SpeechSynthesizer).SpeakAsync($Content)
    }

$Params = @{
    Content = "$Content"
    Title = "ERROR"
    ContentBackground = "WhiteSmoke"
    FontFamily = "Tahoma"
    TitleFontWeight = "Heavy"
    TitleBackground = "Red"
    TitleTextForeground = "YellowGreen"
    Sound = 'Windows Message Nudge'
    ButtonType = 'Cancel-TryAgain-Continue'
    OnLoaded = $Code
}

New-WPFMessageBox @Params

img31

Adding Custom WPF Content

As well as a simple text string, the Content parameter can accept a WPF UI element. When you pass a text string, the function simply creates a textblock element and adds the text. But if you know a little about creating WPF objects in PowerShell, you can create your own elements and add them as content to the message box. This opens up a lot of possibilities and transforms the message box into a kind of easy-to-create window host for any WPF content.

For example, instead of some text, how about displaying a picture instead?


$Source = "C:\Users\tjones\Pictures\minion.jpg"
$Image = New-Object System.Windows.Controls.Image
$Image.Source = $Source
$Image.Height = [System.Drawing.Image]::FromFile($Source).Height / 2
$Image.Width = [System.Drawing.Image]::FromFile($Source).Width / 2

New-WPFMessageBox -Content $Image -Title "Minions Rock!" -TitleBackground LightSeaGreen -TitleTextForeground Black

To set the height and width I have simply halved the dimensions of the source image file.

img17

Maybe you want to add a text description of the picture too. In that case, create a textblock and add both the image and textblock to a layout control like a stackpanel, and pass the stackpanel as content.


$Source = "C:\Users\tjones\Pictures\minion.jpg"
$Image = New-Object System.Windows.Controls.Image
$Image.Source = $Source
$Image.Height = [System.Drawing.Image]::FromFile($Source).Height / 2
$Image.Width = [System.Drawing.Image]::FromFile($Source).Width / 2

$TextBlock = New-Object System.Windows.Controls.TextBlock
$TextBlock.Text = "My friend Robert"
$TextBlock.FontSize = "28"
$TextBlock.HorizontalAlignment = "Center"

$StackPanel = New-Object System.Windows.Controls.StackPanel
$StackPanel.AddChild($Image)
$StackPanel.AddChild($TextBlock)

New-WPFMessageBox -Content $StackPanel -Title "Minions Rock!" -TitleBackground LightSeaGreen -TitleTextForeground Black -ContentBackground LightSeaGreen

img18

How about playing a video?


$MediaPlayer = New-Object System.Windows.Controls.MediaElement
$MediaPlayer.Height = "360"
$MediaPlayer.Width = "640"
$MediaPlayer.Source = "http://video.ch9.ms/ch9/5e56/5148ed00-c3cc-4d1b-a0e0-3f9cbfbb5e56/EmpoerBusinesstodoGreatThings.mp4"

New-WPFMessageBox -Content $MediaPlayer -Title "Windows 10 In The Enterprise"

$MediaPlayer.LoadedBehavior = "Manual"
$MediaPlayer.Stop()

img19

We could also add some buttons to control playback. I’ve added the buttons to a dockpanel, added both the video player and the dockpanel to a stackpanel, as use the stackpanel as content.


# Create a Media Element
$MediaPlayer = New-Object System.Windows.Controls.MediaElement
$MediaPlayer.Height = "360"
$MediaPlayer.Width = "640"
$MediaPlayer.LoadedBehavior = "Manual"
$MediaPlayer.Source = "http://video.ch9.ms/ch9/5e56/5148ed00-c3cc-4d1b-a0e0-3f9cbfbb5e56/EmpoerBusinesstodoGreatThings.mp4"

# Add a start button
$StartButton = New-Object System.Windows.Controls.Button
$StartButton.Content = "Start"
$StartButton.FontSize = 22
$StartButton.Width = "NaN"
$StartButton.Height = "NaN"
$StartButton.VerticalContentAlignment = "Center"
$StartButton.HorizontalContentAlignment = "Center"
$StartButton.HorizontalAlignment = "Center"
$StartButton.VerticalAlignment = "Center"
$StartButton.Background = "Transparent"
$StartButton.Margin = "0,5,15,0"
$StartButton.Padding = 10
$StartButton.Cursor = "Hand"
$StartButton.Add_Click({
    $MediaPlayer.Play()
})

# Add a stop button
$StopButton = New-Object System.Windows.Controls.Button
$StopButton.Content = "Stop"
$StopButton.FontSize = 22
$StopButton.Width = "NaN"
$StopButton.Height = "NaN"
$StopButton.VerticalContentAlignment = "Center"
$StopButton.HorizontalContentAlignment = "Center"
$StopButton.HorizontalAlignment = "Center"
$StopButton.VerticalAlignment = "Center"
$StopButton.Background = "Transparent"
$StopButton.Margin = "15,5,0,0"
$StopButton.Padding = 10
$StopButton.Cursor = "Hand"
$StopButton.Add_Click({
    $MediaPlayer.Stop()
})

# Add a pause button
$PauseButton = New-Object System.Windows.Controls.Button
$PauseButton.Content = "Pause"
$PauseButton.FontSize = 22
$PauseButton.Width = "NaN"
$PauseButton.Height = "NaN"
$PauseButton.VerticalContentAlignment = "Center"
$PauseButton.HorizontalContentAlignment = "Center"
$PauseButton.HorizontalAlignment = "Center"
$PauseButton.VerticalAlignment = "Center"
$PauseButton.Background = "Transparent"
$PauseButton.Margin = "15,5,15,0"
$PauseButton.Padding = 10
$PauseButton.Cursor = "Hand"
$PauseButton.Add_Click({
    $MediaPlayer.Pause()
})

# Add buttons to a dock panel
$DockPanel = New-object System.Windows.Controls.DockPanel
$DockPanel.LastChildFill = $False
$DockPanel.HorizontalAlignment = "Center"
$DockPanel.Width = "NaN"
$DockPanel.AddChild($StartButton)
$DockPanel.AddChild($PauseButton)
$DockPanel.AddChild($StopButton)

# Add dock panel and media player to a stackpanel
$StackPanel = New-object System.Windows.Controls.StackPanel
$StackPanel.AddChild($MediaPlayer)
$StackPanel.AddChild($DockPanel)

New-WPFMessageBox -Content $StackPanel -Title "Windows 10 In The Enterprise"

img20

Although I haven’t included any icons or images for message types like warning or error etc, you could still add your own using custom content:


$Image = New-Object System.Windows.Controls.Image
$Image.Source = "http://www.asistosgb.com/wp-content/uploads/2017/05/attent.png"
$Image.Height = 50
$Image.Width = 50
$Image.Margin = 5

$TextBlock = New-Object System.Windows.Controls.TextBlock
$TextBlock.Text = "The file could not be deleted at this time!"
$TextBlock.Padding = 10
$TextBlock.FontFamily = "Verdana"
$TextBlock.FontSize = 16
$TextBlock.VerticalAlignment = "Center"

$StackPanel = New-Object System.Windows.Controls.StackPanel
$StackPanel.Orientation = "Horizontal"
$StackPanel.AddChild($Image)
$StackPanel.AddChild($TextBlock)

New-WPFMessageBox -Content $StackPanel -Title "WARNING" -TitleFontSize 28 -TitleBackground Orange 

img21

How about including an expander for an error message box, with the error details available for viewing when expanded?


$ComputerName = "RandomPC"
Try
{
    New-PSSession -ComputerName $ComputerName -ErrorAction Stop
}
Catch
{

    # Create a text box
    $TextBox = New-Object System.Windows.Controls.TextBox
    $TextBox.Text = "Could not create a remote session to '$ComputerName'!"
    $TextBox.Padding = 5
    $TextBox.Margin = 5
    $TextBox.BorderThickness = 0
    $TextBox.FontSize = 16
    $TextBox.Width = "NaN"
    $TextBox.IsReadOnly = $True

    # Create an exander
    $Expander = New-Object System.Windows.Controls.Expander
    $Expander.Header = "Error details"
    $Expander.FontSize = 14
    $Expander.Padding = 5
    $Expander.Margin = "5,5,5,0"

    # Bind the expander width to the text box width, so the message box width does not change when expanded
    $Binding = New-Object System.Windows.Data.Binding
    $Binding.Path = [System.Windows.Controls.TextBox]::ActualWidthProperty
    $Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
    $Binding.Source = $TextBox
    [void]$Expander.SetBinding([System.Windows.Controls.Expander]::WidthProperty,$Binding)

    # Create a textbox for the expander
    $ExpanderTextBox = New-Object System.Windows.Controls.TextBox
    $ExpanderTextBox.Text = "$_"
    $ExpanderTextBox.Padding = 5
    $ExpanderTextBox.BorderThickness = 0
    $ExpanderTextBox.FontSize = 16
    $ExpanderTextBox.TextWrapping = "Wrap"
    $ExpanderTextBox.IsReadOnly = $True
    $Expander.Content = $ExpanderTextBox

    # Assemble controls into a stackpanel
    $StackPanel = New-Object System.Windows.Controls.StackPanel
    $StackPanel.AddChild($TextBox)
    $StackPanel.AddChild($Expander)

    # Using no rounded corners as they do not stay true when the window resizes
    New-WPFMessageBox -Content $StackPanel -Title "PSSession Error" -TitleBackground Red -TitleFontSize 20 -Sound 'Windows Unlock' -CornerRadius 0
}

img22

img23

You can also ask the user for input and return the results to a variable in the containing script. For example, here I am simply asking the user to select an option from a drop-down list. The selection will be saved to a variable that can be used later in the script.

Location


Add-Type -AssemblyName PresentationFramework

# Define the location list
$Array = @(
    "London"
    "Paris"
    "New York"
    "Tokyo"
)

# Create a stackpanel container
$StackPanel = New-Object System.Windows.Controls.StackPanel

# Create a combobox
$ComboBox = New-Object System.Windows.Controls.ComboBox
$ComboBox.ItemsSource = $Array
$ComboBox.Margin = "10,10,10,0"
$ComboBox.Background = "White"
$ComboBox.FontSize = 16

# Create a textblock
$TextBlock = New-Object System.Windows.Controls.TextBlock
$TextBlock.Text = "Select your location from the list:"
$TextBlock.Margin = 10
$TextBlock.FontSize = 16

# Assemble the stackpanel
$TextBlock, $ComboBox | foreach {
    $StackPanel.AddChild($PSItem)
}

$Params = @{
    Content = $StackPanel
    Title = "Location Selector"
    TitleFontSize = 22
    TitleFontWeight = "Bold"
    TitleBackground = "CadetBlue"
}

# Display the message
New-WPFMessageBox @Params

# Set the variable from the selected value
$Location = $ComboBox.SelectedValue

If your script returns data in table format, how about using a WPF datagrid to display it in a nicer way? You can populate a datagrid from an array, but I recommend converting to a datatable object as it’s a type that fully supports the features of the datagrid.
In this example, I’m simply displaying services on my system from the Get-Service cmdlet.


# Get Services
$Fields = @(
    'Status'
    'DisplayName'
    'ServiceName'
)
$Services = Get-Service | Select $Fields

# Add Services to a datatable
$Datatable = New-Object System.Data.DataTable
[void]$Datatable.Columns.AddRange($Fields)
foreach ($Service in $Services)
{
    $Array = @()
    Foreach ($Field in $Fields)
    {
        $array += $Service.$Field
    }
    [void]$Datatable.Rows.Add($array)
}

# Create a datagrid object and populate with datatable
$DataGrid = New-Object System.Windows.Controls.DataGrid
$DataGrid.ItemsSource = $Datatable.DefaultView
$DataGrid.MaxHeight = 500
$DataGrid.MaxWidth = 500
$DataGrid.CanUserAddRows = $False
$DataGrid.IsReadOnly = $True
$DataGrid.GridLinesVisibility = "None"

$Params = @{
    Content = $DataGrid
    Title = "Services on $Env:COMPUTERNAME"
    ContentBackground = "WhiteSmoke"
    FontFamily = "Tahoma"
    TitleFontWeight = "Heavy"
    TitleBackground = "LightSteelBlue"
    TitleTextForeground = "Black"
    Sound = 'Windows Message Nudge'
    ContentTextForeground = "DarkSlateGray"
}
New-WPFMessageBox @Params

img24

How about using a TreeView object to group the services by status?


$TreeView = New-Object System.Windows.Controls.TreeView
$TreeView.MaxHeight = 500
$TreeView.Height = 400
$TreeView.Width = 400
$TreeView.BorderThickness = 0
"Running","Stopped" | foreach {
    $Item = New-Object System.Windows.Controls.TreeViewItem
    $Item.Padding = 5
    $Item.FontSize = 16
    $Item.Header = $_
    [void]$TreeView.Items.Add($Item)
}
$TreeView.Items[0].ItemsSource = Get-Service | Where {$_.Status -eq "Running"} | Select -ExpandProperty DisplayName
$TreeView.Items[1].ItemsSource = Get-Service | Where {$_.Status -eq "Stopped"} | Select -ExpandProperty DisplayName

New-WPFMessageBox -Content $TreeView -Title "Services by Status" -TitleBackground "LightSteelBlue"

img25

If you want to use the message box function in your own WPF UI tool, then you can use the WindowHost parameter to pass the host window object. This will make the host window owner of the message box window, and means your message box will always display centered in the host window, no matter the size or location of the window.

In this example I have created a simple host window with a big “Click Me” button. When clicked, a message box will display.


$Window = New-object System.Windows.Window
$Window.WindowStartupLocation = "CenterScreen"
$Window.SizeToContent="WidthAndHeight"
$Button = New-Object System.Windows.Controls.Button
$Button.Content = "Click Me!"
$Button.FontSize = 28
$Button.HorizontalAlignment = "Center"
$Button.VerticalAlignment = "Center"
$Button.Height = 300
$Button.Width = "400"
$Button.Background = "WhiteSmoke"
$Button.BorderThickness = 0
$Window.AddChild($Button)

$Button.Add_Click({
    New-WPFMessageBox -Content "This Content box is owned by the parent window and will be positioned centrally in the window." -Title "WPF Content Box" -TitleBackground Coral -WindowHost $Window
})

$Window.ShowDialog()

img26

img27

My final example is a bit more advanced, but it demonstrates how the message box could be used in a more dynamic way to display output from code running in a background job.

I’ve created a stackpanel from XAML code containing a textbox and a button.  These are all added to a synchronized hash table which gets passed, together with a code block, to a new PowerShell instance which runs in the background. The code block simply pings a list of computers in succession and updates the textbox in real time with the result of each ping.

The background job is triggered by the “Begin” button.


# XAML code
[XML]$Xaml = @"
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    <TextBox ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto" Name="TextBox" Width="400" Height="200" Text="" BorderThickness="0" FontSize="18" TextWrapping="Wrap" FontFamily="Arial" IsReadOnly="True" Padding="5"/>
    <Button Name="Button" Content="Begin" Background="White" FontSize="24" HorizontalAlignment="Center" Cursor="Hand" />
</StackPanel>
"@

# Create the WPF object from the XAML code
$ChildElement = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $xaml))

# Create a synchronised hashtable and add elements to it
$UI = [System.Collections.Hashtable]::Synchronized(@{})
$UI.ChildElement = $ChildElement
$UI.TextBox = $ChildElement.FindName('TextBox')
$UI.Button = $ChildElement.FindName('Button')

# Define the code to run in a background job
$Code = {
    Param($UI)

    # Disable the button during run
    $UI.TextBox.Dispatcher.Invoke({
        $UI.Button.IsEnabled=$False
        $UI.Button.Foreground="Gray"
    })

    # Ping IP addresses
    10..20 | foreach {
        $Index = $_
        $IPaddress = "10.25.24.$Index"
        If (Test-Connection -ComputerName $IPaddress -Count 1 -Quiet)
        {
            $UI.TextBox.Dispatcher.Invoke({
                $UI.TextBox.Text = $UI.TextBox.Text + "`n" + "$IPAddress is online"
                $UI.TextBox.ScrollToEnd()
            })
        }
        Else
        {
            $UI.TextBox.Dispatcher.Invoke({
                $UI.TextBox.Text = $UI.TextBox.Text + "`n" + "$IPAddress could not be contacted"
                $UI.TextBox.ScrollToEnd()
            })
        }
    }

    # Re-enable button
    $UI.TextBox.Dispatcher.Invoke({
        $UI.Button.IsEnabled=$True
        $UI.Button.Foreground="Black"
    })
}

# Define the code to run when the button is clicked
$UI.Button.Add_Click({

    # Spin up a powershell instance and run the code
    $PowerShell = [powershell]::Create()
    $PowerShell.AddScript($Code)
    $PowerShell.AddArgument($UI)
    $PowerShell.BeginInvoke()

})

New-WPFMessageBox -Title "Test-Connection Log Window" -Content $ChildElement

Ping

You could also run the code automatically when the message box opens by using the OnLoaded parameter, or by subscribing to the Loaded event on one of the UI elements instead of the button click, eg:


$UI.TextBox.Add_Loaded({

    # Spin up a powershell instance and run the code
    $PowerShell = [powershell]::Create()
    $PowerShell.AddScript($Code)
    $PowerShell.AddArgument($UI)
    $PowerShell.BeginInvoke()

})

# or...

$OnLoadedCode = {

    # Spin up a powershell instance and run the code
    $PowerShell = [powershell]::Create()
    $PowerShell.AddScript($Code)
    $PowerShell.AddArgument($UI)
    $PowerShell.BeginInvoke()

}

New-WPFMessageBox -Title "Test-Connection Log Window" -Content $ChildElement -OnLoaded $OnLoadedCode<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

And I’m done. Try it out!

Btw, here’s the code 🙂


Function New-WPFMessageBox {
# For examples for use, see my blog:
# https://smsagent.wordpress.com/2017/08/24/a-customisable-wpf-messagebox-for-powershell/
# CHANGES
# 2017-09-11 – Added some required assemblies in the dynamic parameters to avoid errors when run from the PS console host.
# Define Parameters
[CmdletBinding()]
Param
(
# The popup Content
[Parameter(Mandatory=$True,Position=0)]
[Object]$Content,
# The window title
[Parameter(Mandatory=$false,Position=1)]
[string]$Title,
# The buttons to add
[Parameter(Mandatory=$false,Position=2)]
[ValidateSet('OK','OK-Cancel','Abort-Retry-Ignore','Yes-No-Cancel','Yes-No','Retry-Cancel','Cancel-TryAgain-Continue','None')]
[array]$ButtonType = 'OK',
# The buttons to add
[Parameter(Mandatory=$false,Position=3)]
[array]$CustomButtons,
# Content font size
[Parameter(Mandatory=$false,Position=4)]
[int]$ContentFontSize = 14,
# Title font size
[Parameter(Mandatory=$false,Position=5)]
[int]$TitleFontSize = 14,
# BorderThickness
[Parameter(Mandatory=$false,Position=6)]
[int]$BorderThickness = 0,
# CornerRadius
[Parameter(Mandatory=$false,Position=7)]
[int]$CornerRadius = 8,
# ShadowDepth
[Parameter(Mandatory=$false,Position=8)]
[int]$ShadowDepth = 3,
# BlurRadius
[Parameter(Mandatory=$false,Position=9)]
[int]$BlurRadius = 20,
# WindowHost
[Parameter(Mandatory=$false,Position=10)]
[object]$WindowHost,
# Timeout in seconds,
[Parameter(Mandatory=$false,Position=11)]
[int]$Timeout,
# Code for Window Loaded event,
[Parameter(Mandatory=$false,Position=12)]
[scriptblock]$OnLoaded,
# Code for Window Closed event,
[Parameter(Mandatory=$false,Position=13)]
[scriptblock]$OnClosed
)
# Dynamically Populated parameters
DynamicParam {
# Add assemblies for use in PS Console
Add-Type -AssemblyName System.Drawing, PresentationCore
# ContentBackground
$ContentBackground = 'ContentBackground'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.ContentBackground = "White"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ContentBackground, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ContentBackground, $RuntimeParameter)
# FontFamily
$FontFamily = 'FontFamily'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Drawing.FontFamily]::Families.Name | Select -Skip 1
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($FontFamily, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($FontFamily, $RuntimeParameter)
$PSBoundParameters.FontFamily = "Segoe UI"
# TitleFontWeight
$TitleFontWeight = 'TitleFontWeight'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Windows.FontWeights] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.TitleFontWeight = "Normal"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($TitleFontWeight, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($TitleFontWeight, $RuntimeParameter)
# ContentFontWeight
$ContentFontWeight = 'ContentFontWeight'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Windows.FontWeights] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.ContentFontWeight = "Normal"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ContentFontWeight, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ContentFontWeight, $RuntimeParameter)
# ContentTextForeground
$ContentTextForeground = 'ContentTextForeground'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.ContentTextForeground = "Black"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ContentTextForeground, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ContentTextForeground, $RuntimeParameter)
# TitleTextForeground
$TitleTextForeground = 'TitleTextForeground'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.TitleTextForeground = "Black"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($TitleTextForeground, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($TitleTextForeground, $RuntimeParameter)
# BorderBrush
$BorderBrush = 'BorderBrush'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.BorderBrush = "Black"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($BorderBrush, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($BorderBrush, $RuntimeParameter)
# TitleBackground
$TitleBackground = 'TitleBackground'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.TitleBackground = "White"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($TitleBackground, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($TitleBackground, $RuntimeParameter)
# ButtonTextForeground
$ButtonTextForeground = 'ButtonTextForeground'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
$AttributeCollection.Add($ParameterAttribute)
$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$PSBoundParameters.ButtonTextForeground = "Black"
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ButtonTextForeground, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ButtonTextForeground, $RuntimeParameter)
# Sound
$Sound = 'Sound'
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $False
#$ParameterAttribute.Position = 14
$AttributeCollection.Add($ParameterAttribute)
$arrSet = (Get-ChildItem "$env:SystemDrive\Windows\Media" -Filter Windows* | Select -ExpandProperty Name).Replace('.wav','')
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
$AttributeCollection.Add($ValidateSetAttribute)
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($Sound, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($Sound, $RuntimeParameter)
return $RuntimeParameterDictionary
}
Begin {
Add-Type -AssemblyName PresentationFramework
}
Process {
# Define the XAML markup
[XML]$Xaml = @"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot;
x:Name="Window" Title="" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True" Background="Transparent" Opacity="1">
<Window.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border>
<Grid Background="{TemplateBinding Background}">
<ContentPresenter />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Border x:Name="MainBorder" Margin="10" CornerRadius="$CornerRadius" BorderThickness="$BorderThickness" BorderBrush="$($PSBoundParameters.BorderBrush)" Padding="0" >
<Border.Effect>
<DropShadowEffect x:Name="DSE" Color="Black" Direction="270" BlurRadius="$BlurRadius" ShadowDepth="$ShadowDepth" Opacity="0.6" />
</Border.Effect>
<Border.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="DSE" Storyboard.TargetProperty="ShadowDepth" From="0" To="$ShadowDepth" Duration="0:0:1" AutoReverse="False" />
<DoubleAnimation Storyboard.TargetName="DSE" Storyboard.TargetProperty="BlurRadius" From="0" To="$BlurRadius" Duration="0:0:1" AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<Grid >
<Border Name="Mask" CornerRadius="$CornerRadius" Background="$($PSBoundParameters.ContentBackground)" />
<Grid x:Name="Grid" Background="$($PSBoundParameters.ContentBackground)">
<Grid.OpacityMask>
<VisualBrush Visual="{Binding ElementName=Mask}"/>
</Grid.OpacityMask>
<StackPanel Name="StackPanel" >
<TextBox Name="TitleBar" IsReadOnly="True" IsHitTestVisible="False" Text="$Title" Padding="10" FontFamily="$($PSBoundParameters.FontFamily)" FontSize="$TitleFontSize" Foreground="$($PSBoundParameters.TitleTextForeground)" FontWeight="$($PSBoundParameters.TitleFontWeight)" Background="$($PSBoundParameters.TitleBackground)" HorizontalAlignment="Stretch" VerticalAlignment="Center" Width="Auto" HorizontalContentAlignment="Center" BorderThickness="0"/>
<DockPanel Name="ContentHost" Margin="0,10,0,10" >
</DockPanel>
<DockPanel Name="ButtonHost" LastChildFill="False" HorizontalAlignment="Center" >
</DockPanel>
</StackPanel>
</Grid>
</Grid>
</Border>
</Window>
"@
[XML]$ButtonXaml = @"
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; Width="Auto" Height="30" FontFamily="Segui" FontSize="16" Background="Transparent" Foreground="White" BorderThickness="1" Margin="10" Padding="20,0,20,0" HorizontalAlignment="Right" Cursor="Hand"/>
"@
[XML]$ButtonTextXaml = @"
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; FontFamily="$($PSBoundParameters.FontFamily)" FontSize="16" Background="Transparent" Foreground="$($PSBoundParameters.ButtonTextForeground)" Padding="20,5,20,5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
"@
[XML]$ContentTextXaml = @"
<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; Text="$Content" Foreground="$($PSBoundParameters.ContentTextForeground)" DockPanel.Dock="Right" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="$($PSBoundParameters.FontFamily)" FontSize="$ContentFontSize" FontWeight="$($PSBoundParameters.ContentFontWeight)" TextWrapping="Wrap" Height="Auto" MaxWidth="500" MinWidth="50" Padding="10"/>
"@
# Load the window from XAML
$Window = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $xaml))
# Custom function to add a button
Function Add-Button {
Param($Content)
$Button = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ButtonXaml))
$ButtonText = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ButtonTextXaml))
$ButtonText.Text = "$Content"
$Button.Content = $ButtonText
$Button.Add_MouseEnter({
$This.Content.FontSize = "17"
})
$Button.Add_MouseLeave({
$This.Content.FontSize = "16"
})
$Button.Add_Click({
New-Variable -Name WPFMessageBoxOutput -Value $($This.Content.Text) -Option ReadOnly -Scope Script -Force
$Window.Close()
})
$Window.FindName('ButtonHost').AddChild($Button)
}
# Add buttons
If ($ButtonType -eq "OK")
{
Add-Button -Content "OK"
}
If ($ButtonType -eq "OK-Cancel")
{
Add-Button -Content "OK"
Add-Button -Content "Cancel"
}
If ($ButtonType -eq "Abort-Retry-Ignore")
{
Add-Button -Content "Abort"
Add-Button -Content "Retry"
Add-Button -Content "Ignore"
}
If ($ButtonType -eq "Yes-No-Cancel")
{
Add-Button -Content "Yes"
Add-Button -Content "No"
Add-Button -Content "Cancel"
}
If ($ButtonType -eq "Yes-No")
{
Add-Button -Content "Yes"
Add-Button -Content "No"
}
If ($ButtonType -eq "Retry-Cancel")
{
Add-Button -Content "Retry"
Add-Button -Content "Cancel"
}
If ($ButtonType -eq "Cancel-TryAgain-Continue")
{
Add-Button -Content "Cancel"
Add-Button -Content "TryAgain"
Add-Button -Content "Continue"
}
If ($ButtonType -eq "None" -and $CustomButtons)
{
Foreach ($CustomButton in $CustomButtons)
{
Add-Button -Content "$CustomButton"
}
}
# Remove the title bar if no title is provided
If ($Title -eq "")
{
$TitleBar = $Window.FindName('TitleBar')
$Window.FindName('StackPanel').Children.Remove($TitleBar)
}
# Add the Content
If ($Content -is [String])
{
# Replace double quotes with single to avoid quote issues in strings
If ($Content -match '"')
{
$Content = $Content.Replace('"',"'")
}
# Use a text box for a string value…
$ContentTextBox = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ContentTextXaml))
$Window.FindName('ContentHost').AddChild($ContentTextBox)
}
Else
{
# …or add a WPF element as a child
Try
{
$Window.FindName('ContentHost').AddChild($Content)
}
Catch
{
$_
}
}
# Enable window to move when dragged
$Window.FindName('Grid').Add_MouseLeftButtonDown({
$Window.DragMove()
})
# Activate the window on loading
If ($OnLoaded)
{
$Window.Add_Loaded({
$This.Activate()
Invoke-Command $OnLoaded
})
}
Else
{
$Window.Add_Loaded({
$This.Activate()
})
}
# Stop the dispatcher timer if exists
If ($OnClosed)
{
$Window.Add_Closed({
If ($DispatcherTimer)
{
$DispatcherTimer.Stop()
}
Invoke-Command $OnClosed
})
}
Else
{
$Window.Add_Closed({
If ($DispatcherTimer)
{
$DispatcherTimer.Stop()
}
})
}
# If a window host is provided assign it as the owner
If ($WindowHost)
{
$Window.Owner = $WindowHost
$Window.WindowStartupLocation = "CenterOwner"
}
# If a timeout value is provided, use a dispatcher timer to close the window when timeout is reached
If ($Timeout)
{
$Stopwatch = New-object System.Diagnostics.Stopwatch
$TimerCode = {
If ($Stopwatch.Elapsed.TotalSeconds -ge $Timeout)
{
$Stopwatch.Stop()
$Window.Close()
}
}
$DispatcherTimer = New-Object -TypeName System.Windows.Threading.DispatcherTimer
$DispatcherTimer.Interval = [TimeSpan]::FromSeconds(1)
$DispatcherTimer.Add_Tick($TimerCode)
$Stopwatch.Start()
$DispatcherTimer.Start()
}
# Play a sound
If ($($PSBoundParameters.Sound))
{
$SoundFile = "$env:SystemDrive\Windows\Media\$($PSBoundParameters.Sound).wav"
$SoundPlayer = New-Object System.Media.SoundPlayer -ArgumentList $SoundFile
$SoundPlayer.Add_LoadCompleted({
$This.Play()
$This.Dispose()
})
$SoundPlayer.LoadAsync()
}
# Display the window
$null = $window.Dispatcher.InvokeAsync{$window.ShowDialog()}.Wait()
}
}

156 thoughts on “A Customisable WPF MessageBox for PowerShell

    1. If ($WPFMessageBoxOutput -eq “YES”)
      {
      [system.Diagnostics.Process]::Start(“msedge”,”https://smsagent.blog/2017/08/24/a-customisable-wpf-messagebox-for-powershell/”)
      }
      else
      {
      ….. DO Someting
      }

  1. Hi Awesome tools for customization.
    One question

    Is it possible to display the content in multiple line? How?
    Example:
    “Select user
    OR
    Press Exit”

      1. Hi
        In the meantime I found a solution – if you look at the comment from “Mark Cherry – December 16, 2021 at 8:34 am”, I could use that to create a working solution

        Best regards
        ‘Adder

  2. Hello,
    I tried to open a site when the message box closes and/or when you press a specific button but this does not seem to work.
    What am I doing wrong?

    $OnClosed = {
    [system.Diagnostics.Process]::Start(“msedge”,”https://www.google.com/”)
    }

    $Params = @{
    Content = $StackPanel
    Title = “NOTIFICATION – Password”
    TitleFontSize = 28
    TitleBackground = ‘Orange’
    FontFamily = “Segoe UI”
    ButtonType = ‘None’
    CustomButtons = “How-to”,”OK”
    }

    New-WPFMessageBox @Params
    If ($WPFMessageBoxOutput -eq “How-to”)
    {
    [system.Diagnostics.Process]::Start(“msedge”,”https://smsagent.blog/2017/08/24/a-customisable-wpf-messagebox-for-powershell/”)
    }

  3. Minified.
    Removed buttons except for Custom.
    Removed Sound.

    Function New-WPFMessageBox {
    # https://smsagent.wordpress.com/2017/08/24/a-customisable-wpf-messagebox-for-powershell/
    # minified with many varieties of notepad++ regex expression: \r\n(.*?)\r\n\r\n
    # Removed buttons except for Custom
    # Removed Sound

    # Define Parameters
    [CmdletBinding()]
    Param
    ([Parameter(Mandatory=$True,Position=0)][Object]$Content,[Parameter(Mandatory=$false,Position=1)][string]$Title,[Parameter(Mandatory=$false,Position=2)][ValidateSet(‘OK’,’OK-Cancel’,’Abort-Retry-Ignore’,’Yes-No-Cancel’,’Yes-No’,’Retry-Cancel’,’Cancel-TryAgain-Continue’,’None’)][array]$ButtonType = ‘OK’,[Parameter(Mandatory=$false,Position=3)][array]$CustomButtons,[Parameter(Mandatory=$false,Position=4)][int]$ContentFontSize = 14,[Parameter(Mandatory=$false,Position=5)][int]$TitleFontSize = 14,[Parameter(Mandatory=$false,Position=6)][int]$BorderThickness = 0,[Parameter(Mandatory=$false,Position=7)][int]$CornerRadius = 8,[Parameter(Mandatory=$false,Position=8)][int]$ShadowDepth = 3,[Parameter(Mandatory=$false,Position=9)][int]$BlurRadius = 20,[Parameter(Mandatory=$false,Position=10)][object]$WindowHost,[Parameter(Mandatory=$false,Position=11)][int]$Timeout,[Parameter(Mandatory=$false,Position=12)][scriptblock]$OnLoaded,[Parameter(Mandatory=$false,Position=13)][scriptblock]$OnClosed)DynamicParam {Add-Type -AssemblyName System.Drawing, PresentationCore
    $ContentBackground = ‘ContentBackground’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary;$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet);$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.ContentBackground = “White”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ContentBackground, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($ContentBackground, $RuntimeParameter);$FontFamily = ‘FontFamily’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Drawing.FontFamily]::Families.Name | Select -Skip 1 ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet);$AttributeCollection.Add($ValidateSetAttribute);$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($FontFamily, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($FontFamily, $RuntimeParameter);$PSBoundParameters.FontFamily = “Segoe UI”;$TitleFontWeight = ‘TitleFontWeight’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Windows.FontWeights] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet);$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.TitleFontWeight = “Normal”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($TitleFontWeight, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($TitleFontWeight, $RuntimeParameter);$ContentFontWeight = ‘ContentFontWeight’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Windows.FontWeights] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet);$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.ContentFontWeight = “Normal”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ContentFontWeight, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($ContentFontWeight, $RuntimeParameter);$ContentTextForeground = ‘ContentTextForeground’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet);$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.ContentTextForeground = “Black”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ContentTextForeground, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($ContentTextForeground, $RuntimeParameter);$TitleTextForeground = ‘TitleTextForeground’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet);$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.TitleTextForeground = “Black”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($TitleTextForeground, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($TitleTextForeground, $RuntimeParameter);$BorderBrush = ‘BorderBrush’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) ;$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.BorderBrush = “Black”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($BorderBrush, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($BorderBrush, $RuntimeParameter);$TitleBackground = ‘TitleBackground’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) ;$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.TitleBackground = “White”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($TitleBackground, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($TitleBackground, $RuntimeParameter);$ButtonTextForeground = ‘ButtonTextForeground’;$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute];$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute;$ParameterAttribute.Mandatory = $False;$AttributeCollection.Add($ParameterAttribute) ;$arrSet = [System.Drawing.Brushes] | Get-Member -Static -MemberType Property | Select -ExpandProperty Name ;$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet) ;$AttributeCollection.Add($ValidateSetAttribute);$PSBoundParameters.ButtonTextForeground = “Black”;$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ButtonTextForeground, [string], $AttributeCollection);$RuntimeParameterDictionary.Add($ButtonTextForeground, $RuntimeParameter);return $RuntimeParameterDictionary};Begin {Add-Type -AssemblyName PresentationFramework};Process {
    [XML]$Xaml = @”

    “@;[XML]$ButtonXaml = @”

    “@;[XML]$ButtonTextXaml = @”

    “@;[XML]$ContentTextXaml = @”

    “@;$Window = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $xaml));Function Add-Button {Param($Content);$Button = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ButtonXaml));$ButtonText = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ButtonTextXaml));$ButtonText.Text = “$Content”;$Button.Content = $ButtonText;$Button.Add_MouseEnter({;$This.Content.FontSize = “17”;});$Button.Add_MouseLeave({;$This.Content.FontSize = “16”;});$Button.Add_Click({; New-Variable -Name WPFMessageBoxOutput -Value $($This.Content.Text) -Option ReadOnly -Scope global -Force;$Window.Close();});$Window.FindName(‘ButtonHost’).AddChild($Button)};If ($ButtonType -eq “None” -and $CustomButtons){Foreach ($CustomButton in $CustomButtons){Add-Button -Content “$CustomButton”}};If ($Content -is [String]){If ($Content -match ‘”‘){$Content = $Content.Replace(‘”‘,”‘”);};$ContentTextBox = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ContentTextXaml));$Window.FindName(‘ContentHost’).AddChild($ContentTextBox)}Else{Try{$Window.FindName(‘ContentHost’).AddChild($Content) ;}Catch{$_;}};$Window.FindName(‘Grid’).Add_MouseLeftButtonDown({;$Window.DragMove()});If ($OnLoaded){$Window.Add_Loaded({$This.Activate();Invoke-Command $OnLoaded;})}Else{$Window.Add_Loaded({$This.Activate()})};If ($WindowHost){;$Window.Owner = $WindowHost;$Window.WindowStartupLocation = “CenterOwner”;};$null = $window.Dispatcher.InvokeAsync{$window.ShowDialog()}.Wait()}}

    $Params = @{
    Content = “”
    Title = “Are you on a public or private PC?”
    TitleFontSize = 20
    TitleBackground = ‘Green’
    TitleTextForeground = ‘White’
    ButtonType = ‘None’
    CustomButtons = “PUBLIC”,”PRIVATE”, “EXIT”
    }
    New-WPFMessageBox @Params

  4. I’m using this function but the WPFMessageBoxOutput is not filled. There for the If and Else statements are not running.

    Can I Please get some help in this.

    1. Hi, I’m no expert in WPF but I found that I had to modify the scope of the PowerShell variable in the provided script at line 304.

      New-Variable -Name WPFMessageBoxOutput -Value $($This.Content.Text) -Option ReadOnly -Scope Global -Force

      The scope in the provided script is set to ‘Script’, which limits access to the variable to within the script. So, if your code is referencing the script from another script of from the console, the variable is not accessible.

      If you added your code to the provided script, i.e. within the same file, you would be able to access the variable.

      I probably haven’t explained this very well. Please do a search for ‘PowerShell Variable Scopes’. e.g. https://4sysops.com/archives/the-powershell-variable-scope/

      1. Thank you for this. I was trying to find a way to have WPFMessageBoxOutput give a response for a timeout. Current code will regurgitate previous answer.

        I modified your line to (my line 312) to remove readonly:

        New-Variable -Name WPFMessageBoxOutput -Value $($This.Content.Text) -Scope Global -Force

        AND

        I modified the $TimerCode to (my line 458):

        $TimerCode = {
        If ($Stopwatch.Elapsed.TotalSeconds -ge $Timeout)
        {
        $WPFMessageBoxOutput = “TIMED_OUT”
        $Stopwatch.Stop()
        $Window.Close()
        }
        }

  5. Hi

    I’m trying to make some dynamic text in a messagebox, but in order to have the data presented correctly I need multiline functionality… but I can’t get this to work – I tried the following ways in order to obtain the multilines within my variable:

    $contentText = “The following services are running:`n # Internet Explorer`n # Microsoft Teams`nAll running programs will be shut down”

    $contentText = “The following services are running:`r`n # Internet Explorer`r`n # Microsoft Teams`r`nAll running programs will be shut down”

    $contentText = “The following services are running:{0} # Internet Explorer{0} # Microsoft Teams{0}All running programs will be shut down” -f [environment]::NewLine

    $contentText = @”
    The following programs are running:
    # Internet Explorer
    # Microsoft Teams
    All running programs will be shut down
    “@

    $contentText = “The following services are running
    # Internet Explorer{0}
    # Microsoft Teams{0}
    All running programs will be shut downDu vil nu få installeret Teams”

    none of them is working… and now I’m all out of ideas?

    Anyone?

    Thanks
    ‘Adder

    1. I figured this out
      the correct format to add a line is to use
      -message “This is my first line This is my second”

      The issue is xml is using that language and we are passing from powershell to xml
      Now also found another issue
      In this script he is attempting to change ” to ‘ in a replace statement but he calls $content prior in his xml statement, so this change never happens unless you reload the initial statement.

      So i fixed that part, and also added a part in to replace `r`n with
      Then reload the xml statement
      Then post it.

      Your Welcome.
      Modifications below

      If ($Content -is [String])
      {
      Write-Host $Content
      # Replace double quotes with single to avoid quote issues in strings
      If ($Content -match ‘”‘)
      {
      Write-Host “Replaced quotes”
      $Content = $Content.Replace(‘”‘,”‘”)

      }

      If ($Content -match ‘`r`n’){
      Write-Host “Replacing newline”
      $Content = $Content.Replace(‘`r`n’,’ ’)

      }

      Write-Host $Content
      # Use a text box for a string value…
      #need to reload the xml because $content is already loaded this is missed in the original code
      [XML]$ContentTextXaml = @”

      “@
      #now we can load that again
      $ContentTextBox = [Windows.Markup.XamlReader]::Load((New-Object -TypeName System.Xml.XmlNodeReader -ArgumentList $ContentTextXaml))
      $Window.FindName(‘ContentHost’).AddChild($ContentTextBox)
      }

  6. I didn’t have a lot of time when I posted my last reply, but this morning I had a look at the PowerShell script that I created when I used the WPFMessageBox. It looks like I used a StackPanel with TextBlocks to format information for the user. Would something like this help you?

    Add-Type -AssemblyName PresentationFramework

    # Create a stackpanel container
    $StackPanel = New-Object System.Windows.Controls.StackPanel

    # Create a textblock
    $TextBlock = New-Object System.Windows.Controls.TextBlock
    $TextBlock.Text = “An Admin Level PowerShell session is about to be launched to enable you to install developer applications on this machine.”
    $TextBlock.Margin = “10,10,10,3”
    $TextBlock.FontSize = 16
    $TextBlock.TextWrapping = [System.Windows.TextWrapping]::Wrap

    $TextBlock2 = New-Object System.Windows.Controls.TextBlock
    $TextBlock2.Inlines.Add([System.Windows.Documents.Run]::new(“Please select “));
    $TextBlock2.Inlines.Add([System.Windows.Documents.Bold]::new([System.Windows.Documents.Run]::new(“OK”)));
    $TextBlock2.Inlines.Add([System.Windows.Documents.Run]::new(” if you need to install these applications.”));
    $TextBlock2.Margin = “10,10,10,3”
    $TextBlock2.FontSize = 16
    $TextBlock2.TextWrapping = [System.Windows.TextWrapping]::Wrap

    $TextBlock3 = New-Object System.Windows.Controls.TextBlock
    $TextBlock3.Inlines.Add([System.Windows.Documents.Run]::new(“Select “));
    $TextBlock3.Inlines.Add([System.Windows.Documents.Bold]::new([System.Windows.Documents.Run]::new(“Cancel”)));
    $TextBlock3.Inlines.Add([System.Windows.Documents.Run]::new(” if you wish to install them at a later date.”));
    $TextBlock3.Margin = “10,10,10,3”
    $TextBlock3.FontSize = 16
    $TextBlock3.TextWrapping = [System.Windows.TextWrapping]::Wrap

    $TextBlock4 = New-Object System.Windows.Controls.TextBlock
    $TextBlock4.Inlines.Add([System.Windows.Documents.Run]::new(“Select “));
    $TextBlock4.Inlines.Add([System.Windows.Documents.Bold]::new([System.Windows.Documents.Run]::new(“Do not show this again”)));
    $TextBlock4.Inlines.Add([System.Windows.Documents.Run]::new(” and “));
    $TextBlock4.Inlines.Add([System.Windows.Documents.Bold]::new([System.Windows.Documents.Run]::new(“Cancel”)));
    $TextBlock4.Inlines.Add([System.Windows.Documents.Run]::new(” if you do not want to run the installer process again.”));
    $TextBlock4.Margin = “10,10,10,10”
    $TextBlock4.FontSize = 16
    $TextBlock4.TextWrapping = [System.Windows.TextWrapping]::Wrap

    # Create a checkbox
    $CheckBox = New-Object System.Windows.Controls.CheckBox
    $CheckBox.IsThreeState = $false
    $CheckBox.Margin = “10,10,10,0”
    $CheckBoxText = New-Object System.Windows.Controls.TextBlock
    $CheckBoxText.Text = “Do not show this again”
    $CheckBoxText.Margin = “10,-3,0,0”
    $CheckBoxText.VerticalAlignment = “Top”
    $CheckBoxText.FontSize = 16
    $CheckBox.AddChild($CheckBoxText)

    $StackPanel = New-Object System.Windows.Controls.StackPanel
    $StackPanel.Orientation = “Vertical”
    $StackPanel.Width = 500

    # Assemble the stackpanel
    $TextBlock, $TextBlock2, $TextBlock3, $TextBlock4, $CheckBox | foreach {
    $StackPanel.AddChild($PSItem)
    }

    $StackPanel2 = New-Object System.Windows.Controls.StackPanel
    $StackPanel2.Orientation = “Vertical”

    # Assemble the stackpanel
    $StackPanel | foreach {
    $StackPanel2.AddChild($PSItem)
    }

    $Params = @{
    Content = $StackPanel2
    Title = “ID Developer Application Installer”
    TitleFontSize = 22
    TitleFontWeight = “Bold”
    TitleBackground = “DarkOrange”
    ButtonType = “OK-Cancel”
    }

    # Display the message
    New-WPFMessageBox @Params

    # Set the variable from the selected value
    $Disable = $CheckBox.IsChecked

    if ($Disable -eq $true) {
    # Disable the scheduled job that launches this script
    # …
    }

    if($WPFMessageBoxOutput -eq “Cancel”) {
    Exit
    }

    1. Hi Mark

      That was exactly what I needed to solve the job – thanks a lot mate, and happy holidays for you and yours 🙂

      Best regards
      ‘Adder

  7. Hi
    Just an idea for enhancement:

    I had the need for having the messagebox appear alway on top, so I added ‘Topmost=”True”‘ to the line starting with “x:Name=” (line 226) in the script

    That might be an feature to implement?

    Best regards and happy holidays

    ‘Adder

  8. I’m completely clueless here. I’ve tried doing this in powershell, and in Visual Studio. Nothing seems to work. I guess you could say I don’t know what I’m doing. I’d love to branch into this type of script, but I don’t seem to know how to start. Even with the simplest script at the top of this page, I don’t know how to make that work.

    1. Hi Kevin
      I would be happy to help if I can – but I’m not sure what your question is, can you elaborate?

      Best regards
      ‘Adder

      1. Sorry, let me try to be more accurate in what I’m saying. I’m not a coder. I’ve written tons of scripts here where I work, and I’m trying to branch out, but I don’t know what I’m doing. I see this page, which talks about WPF, and there are many examples here on this page, lots of seemingly simple little one, two, and three line scripts, that seem to create beautiful little windows.

        So, I’d love to try doing this myself, but I don’t know… how. What software do I use? Like I said, I have installed Visual Studio. Do I do this in Visual Studio? Do I do this in Powershell? More specifically, let’s start with that first line at the top of this page:

        New-WPFMessageBox -Content “I’m a WPF Object!”

        Supposedly this one little line creates a window with that text. But how? Do I compile this? If so, in what? Do I just put it in a text file and change the extension? I do not know what to do with this line of code.

  9. Hi Kevin

    I think I know where you are running into trouble – you are not using the function Trevor provided… if you search for “Btw, here’s the code 🙂” just below you find the function needed, and then combine it with the codeblock needed for the messagebox.

    I’ve made a short example – beware the function is shortened down by removing the middle part of it 🙂

    Function New-WPFMessageBox {

    # For examples for use, see my blog:
    # https://smsagent.wordpress.com/2017/08/24/a-customisable-wpf-messagebox-for-powershell/

    # CHANGES
    # 2017-09-11 – Added some required assemblies in the dynamic parameters to avoid errors when run from the PS console host.

    # Define Parameters
    [CmdletBinding()]
    .
    .
    .
    # Display the window
    $null = $window.Dispatcher.InvokeAsync{$window.ShowDialog()}.Wait()

    }
    }

    New-WPFMessageBox -Content “I’m a WPF Object!” -Title “Custom Message Box” -TitleBackground CornflowerBlue

    Best regards
    ‘Adder

    1. Yes, I saw that. I copied it and pasted it into visual studio and it gives me all kinds of errors. This is what I’m trying to say. I do not know what to do with this code. In what environment do I work with it? For someone who knows nothing about this type of code, but wants to know, there’s absolutely zero on this page that tells me what to do with it. Just reading this page, it makes it seem like I can just take that one line of code, “New-WPFMessageBox -Content “I’m a WPF Object!”” and just make a neat little window with it. But that’s apparently not the case.

      So please, if you can understand what I’m saying here, tell me what to do with this code.

      1. PowerShell as a language/tool uses commandlets to perform functions. They’re comprised of verb-noun pairs (“new” is used as a verb, in PowerShell speak). This WPF Message Box tool is a custom function, which provides a custom commandlet. In order to make it available to your PowerShell session, you have to first define it. I find that this is easiest to do in the PowerShell ISE (powershell_ise.exe from the Run dialog). When you open the ISE, there will be a top white part and a bottom blue part. The bottom blue part is an active PowerShell session. The top white part is where you can write and edit your script.

        Copy the function from the page above into the white part, then press F5 on the keyboard (or press the play button in the ISE). This executes all the code in the white part, which in this case is simply defining the function “new-WPFMessageBox”. Now that the PowerShell session understands what that commandlet means, you can use it by typing that one line you posted a moment ago. For an understanding of what this all means, you can search for “using custom functions in PowerShell”.

        You can also customise it as described above. Here’s how I use it normally, using a technique called “splatting”:
        $MessageBoxDetails = @{
        Content = “Unable to find a source folder to restore, quitting process.”
        Title = “No Sources Found”
        TitleFontSize = 20
        TitleBackground = ‘Red’
        TitleTextForeground = ‘White’
        ButtonType = ‘Ok’
        Timeout = 30
        } #close MessageBoxDetails
        New-WPFMessageBox @MessageBoxDetails

        To use this example, copy/paste this stuff into your ISE, then highlight it and press F8 to run the selection (or press the other play button just next to the stop sign in the ISE ribbon).

  10. hi~
    Is it possible to add a countdown to the popup window to prompt the user
    How much time does he have to get ready to reboot?
    I’ve tried many methods but it doesn’t work
    Can someone give any hints?

    1. Hi,

      Managed to a countdown finally. here is an example:

      # Set banner’s image:

      $Image = New-Object System.Windows.Controls.Image

      $Image.Source = “C:Tempimage.jpg”

      $Image.Height = [System.Drawing.Image]::FromFile($Source).Height / 2

      $Image.Width = [System.Drawing.Image]::FromFile($Source).Width / 2

      $Image.HorizontalAlignment = “Center”

      # Set text (regular or bold):

      $TextBlock = New-Object System.Windows.Controls.TextBlock

      $TextBlock.Inlines.Add([System.Windows.Documents.Bold]::new([System.Windows.Documents.Run]::new(“Hello $env:USERNAME `n”)));

      $TextBlock.Inlines.Add([System.Windows.Documents.Run]::new(“Your virtual machine was automatically rebooted on “));

      $TextBlock.Inlines.Add([System.Windows.Documents.Bold]::new([System.Windows.Documents.Run]::new(“02:00PM `n”)));

      $TextBlock.Inlines.Add([System.Windows.Documents.Run]::new(“as part of our Reboot policy.`n“));

      $TextBlock.Inlines.Add([System.Windows.Documents.Run]::new(”For any questions, please contact IT Support.“));

      $TextBlock.Margin = “10,10,10,10”

      $TextBlock.FontSize = 14

      $TextBlock.LineHeight = 22

      $TextBlock.TextWrapping = [System.Windows.TextWrapping]::Wrap

      # Set countdown parameters:

      $CountdownTextBlock = New-Object System.Windows.Controls.TextBlock

      $CountdownTextBlock.Name = “CountdownTextBlock”

      $CountdownTextBlock.Margin = “10,10,10,10”

      $CountdownTextBlock.FontSize = 13

      $CountdownTextBlock.HorizontalAlignment = “Center”

      $CountdownTextBlock.VerticalAlignment = “Center”

      # Add all data to the StackPanel:  

      $StackPanel = New-Object System.Windows.Controls.StackPanel

      $StackPanel.AddChild($Image)

      $StackPanel.AddChild($TextBlock)

      $StackPanel.AddChild($CountdownTextBlock)

      $Params = @{

          Content              = $StackPanel

          Title                = “Machine Reboot”

          TitleFontSize        = 18

          TitleBackground      = ‘Black’

          TitleTextForeground  = ‘White’

          FontFamily           = “Segoe UI”

          ButtonType           = ‘None’

          CustomButtons        = “OK”

          ButtonTextForeground = “DarkOrange”

          Timeout              = 60

          OnLoaded             = {

              $DispatcherTimer = New-Object System.Windows.Threading.DispatcherTimer

              $DispatcherTimer.Interval = [TimeSpan]::FromSeconds(1)

              $DispatcherTimer.Add_Tick({

                      $Params.Timeout–

                      $CountdownTextBlock.Text = ”  This message will close automatically`n”

                      $CountdownTextBlock.Text += ”                   in $($Params.Timeout) seconds.”

                      If ($Params.Timeout -le 0) {

                          $DispatcherTimer.Stop()

                          $Window.Close()

                      }

                  })

              $DispatcherTimer.Start()

          }

      }

      New-WPFMessageBox @Params

  11. Is it possible to add Wpf button pressed effect?
    So when you click a Costumebutton, the button will blink or switch color, so you can see that it has been pressed and selected.

  12. Is there a way to change the color of the text inside the text box so they are different.
    Installation is complete – BLACK
    Your computer will restart in xxxx. – RED

      1. Thanks will give that a go.
        This is great work BTW. Exactly what I’ve been looking for.

  13. I would need a way to have the buttons to click outlined, so for users it is easier to understand that buttons are buttons and not text. Is ther a way to do that?

    1. Hi Aviv

      If you look at the 21. december 2021 you will find:

      “I had the need for having the messagebox appear alway on top, so I added ‘Topmost=”True”‘ to the line starting with “x:Name=” (line 226) in the script”

      Best regards
      ‘Adder

  14. Added the WPFMessageBoxOutput to the Timeout code to help with if-else statements for when a window times out.

    $TimerCode = {
    If ($Stopwatch.Elapsed.TotalSeconds -ge $Timeout)
    {
    $Stopwatch.Stop()
    $Window.Close()
    New-Variable -Name WPFMessageBoxOutput -Value “TIMED_OUT” -Scope Global -Force -Option ReadOnly
    }
    }

    I just added :
    New-Variable -Name WPFMessageBoxOutput -Value “TIMED_OUT” -Scope Global -Force -Option ReadOnly

  15. is it possible to add timeout in hh:m:ss format and stays the popup on screen with high priority i.e. should not get to backgroup of other apps

  16. What an awesome script, its exactly what I’ve been looking for. Does anyone know how to get this to display on other PCs on the same domain using Invoke-Command?

    1. Function AskUserinput ()
      {
      $TextBlock = New-Object System.Windows.Controls.TextBlock
      $TextBlock.Text = “Enter your name.”
      $TextBlock.Padding = 10
      $TextBlock.FontFamily = “Verdana”
      $TextBlock.FontSize = 16
      $TextBlock.VerticalAlignment = “Center”

      $TextBox = New-Object System.Windows.Controls.TextBox
      $TextBox.Text = “”
      $TextBox.FontSize = 16
      $TextBox.Width = “300”

      # Assemble controls into a stackpanel
      $StackPanel = New-Object System.Windows.Controls.StackPanel
      $StackPanel.AddChild($TextBlock)
      $StackPanel.AddChild($TextBox)

      $WPFParams = @{
      Content = $StackPanel
      Title = “What is your name?”
      TitleFontSize = 20
      TitleBackground = ‘Green’
      TitleTextForeground = ‘White’
      ButtonType = “OK-Cancel”
      }

      # Display the message
      New-WPFMessageBox @WPFParams

      if ( ($WPFMessageBoxOutput -eq “Cancel”) -OR ($TextBox.Text -eq “”) ) {
      Exit 1
      } else {
      Return $TextBox.Text
      }
      }

      $UserInput = AskUserInput()

  17. Have been using this for a while with success. Recently added it to our Task Sequence for OS deployment to let our technicians know that either the image was successfully, or the image failed.

    Also increased the timeout on error messages in OSD from 15 minutes to 24 hours to ensure they see the message.

    Problem we are having is, if the screen goes blank and the technician clicks on the mouse pad it sends the message box to the background and all they are left is with a spinning wheel. Is there a way main the focus on the message box until either OK or EXIT is clicked.

  18. Hello,
    This is an awesome scritpt and thank you! Can the timeout function be modified to have a preset value?

    Thanks again,
    Rob

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.