7

I wonder is there an API for creating custom notifications in Windows 10 style like the one showing when updates are available for example?

enter image description here

I know that Citrix can send messages showing like that and it seems they use the sessionmsg.exe. Unfortunately, I cannot find any help on the parameter the exe supports.

Also, an API would be preferred.

Another thing: How do you call this kind of message? Banner? Message? MessageBox? SystemMessage?

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
B4DschK4Pp
  • 165
  • 1
  • 10
  • What is being sought is UWP's `[Windows.UI.PopUps.MessageDialog]`, but I do not think it is possible to call from PowerShell. The best that can be done is to style a window with XAML to make it look similar, which is far from a one liner. There aren't any quick solutions here. Even in C# you need to do this within a specific UWP project. Some `[Windows.UI]` stuff does work, like the notifications you would get in the bottom right hand corner of the screen. – Ash Mar 23 '22 at 22:46
  • Are you looking for Toast notifications? https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-ux-guidance There's also MessageDialog https://learn.microsoft.com/en-us/uwp/api/windows.ui.popups.messagedialog – Simon Mourier Mar 25 '22 at 12:14

3 Answers3

5

After reading the question, I found it interesting my selves to investigate possibilities and share my founds as the answer.

As you tagged your question with C#, my solution will be based on C#. By and btw I was not able to find the default api to do the job, if other finds a solution with an example I will be happy to vote for it.

I started with the UWP solution and created a very simple information dialog using ContentDialog. Create a UWP project and add the following code:

private async void ShowMessage()
{
    ContentDialog dialog = new ContentDialog
    {
        Title = "Title",
        Content = "Content text",
        Width = 200,
        Height = 600,
        Background = new SolidColorBrush(Colors.CornflowerBlue),
        Foreground = new SolidColorBrush(Colors.White),
        BorderThickness = new Thickness(1),
        BorderBrush = new SolidColorBrush(Colors.White),
        CloseButtonText = "Close",
    };

    await dialog.ShowAsync();
}

public MainPage()
{
    this.InitializeComponent();
    ShowMessage();
}

This will create something like

enter image description here

But that content dialog appears as a part of the application and not the windows system as I tried to solve.

Adding the following lines before the ShowMessage method will maximize application to the whole background of the screen.

ApplicationView.GetForCurrentView().SuppressSystemOverlays = true;
ApplicationView.GetForCurrentView().FullScreenSystemOverlayMode = FullScreenSystemOverlayMode.Minimal;
ApplicationView.GetForCurrentView().TryEnterFullScreenMode();

But IMO it is not the best solution. I thought, there might be another way, I tried with WPF instead.

I created a WPF project, my strategy was to start MainWindow in minimized mode and the content dialog to appear. Hence there is no content dialog in WPF like UWP, I created something similar (look and feel).

Here is the code in my MainWindow

private readonly string _title;
private readonly string _message;

public MainWindow()
{
    _title = "Updates are available";
    _message = "Required updates need to be downloaded.";
    InitializeComponent();

    string[]? args = App.Args;
    if (args != null && args.Length > 0)
    {
        _title = args[0];
        _message = args[1];
    }

    MinimizedMainWindow();
    ShowContentDialog();
}

protected void MinimizedMainWindow()
{
    AllowsTransparency = true;
    WindowStyle = WindowStyle.None;
    WindowState = WindowState.Maximized;
    Background = Brushes.Transparent;
    Topmost = true;
}

public void ShowContentDialog()
{
    ContentDialog dialog = new ContentDialog
    {
        Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF0066CC")),
        Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFFFFFFF")),
        WindowStartupLocation = WindowStartupLocation.CenterScreen,
        SizeToContent = SizeToContent.WidthAndHeight,
        WindowStyle = WindowStyle.None,
        Padding = new Thickness(20),
        Margin = new Thickness(0),
        ResizeMode = ResizeMode.NoResize,
        Width = 600,
        Height = 200,
        Title = { Text = _title },
        Message = { Text = _message }
    };

    dialog.Show();
}

And here is my ContentDialog.xaml

<Window x:Class="NotificationSol.ContentDialog"
        xmlns:local="clr-namespace:NotificationSol"
        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:av="http://schemas.microsoft.com/expression/blend/2008"
        mc:Ignorable="d"
        Title="ContentDialog" 
        av:DesignWidth="600" 
        av:DesignHeight="200"
        >
    <Grid>
        <TextBlock x:Name="Title" Margin="30,22,30,118" TextWrapping="Wrap" Text="Title" FontSize="28"/>
        <TextBlock x:Name="Message" Margin="30,70,30,70" TextWrapping="Wrap" Text="Content" FontSize="16"/>
        <Button x:Name="Button" Click="CloseButton_Click" Content="Close" HorizontalAlignment="Right" Margin="0,0,30,20" VerticalAlignment="Bottom" Width="75" Background="#FF0066CC" BorderBrush="White" Foreground="White" Padding="8,4"/>
    </Grid>
</Window>

And ContentDialog.xaml.cs

public ContentDialog()
{
    InitializeComponent();
}

private void CloseButton_Click(object sender, RoutedEventArgs e)
{
    Close();
    base.OnClosed(e);
    Application.Current.Shutdown();
}

To let my application take parameters in the command line, I made the following changes to App.xaml.cs:

public static string[]? Args;
void AppStartup(object sender, StartupEventArgs e)
{
    if (e.Args.Length > 0)
    {
        Args = e.Args;
    }
}

And to App.xaml adding as the startup

<Application x:Class="NotificationSol.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:NotificationSol"
             StartupUri="MainWindow.xaml"
             Startup="AppStartup">
    <Application.Resources>
         
    </Application.Resources>
</Application>

Running this code from the visual studio I get the:

enter image description here

If you run the software with 2 parameters from the command line, you can pass the title and the message. You have the code, where you can create other method actions or buttons with other features. Right now my button is just closing the dialog box and the application. Here I have put both examples on my repo:

https://github.com/maythamfahmi/BlogExamples/tree/master/Stackoverflow/ContentDialog

Maytham Fahmi
  • 31,138
  • 14
  • 118
  • 137
1

You need to create a function using Winforms and WPF assemblies to create the pop up.

Functions

Function New-WPFDialog() {
        <#
        .SYNOPSIS
        This neat little function is based on the one from Brian Posey's Article on Powershell GUIs
    
    .DESCRIPTION
      I re-factored a bit to return the resulting XaML Reader and controls as a single, named collection.

    .PARAMETER XamlData
     XamlData - A string containing valid XaML data

    .EXAMPLE

      $MyForm = New-WPFDialog -XamlData $XaMLData
      $MyForm.Exit.Add_Click({...})
      $null = $MyForm.UI.Dispatcher.InvokeAsync{$MyForm.UI.ShowDialog()}.Wait()

    .NOTES
    Place additional notes here.

    .LINK
      http://www.windowsnetworking.com/articles-tutorials/netgeneral/building-powershell-gui-part2.html

    .INPUTS
     XamlData - A string containing valid XaML data

    .OUTPUTS
     a collection of WPF GUI objects.
  #>
    
    Param([Parameter(Mandatory = $True, HelpMessage = 'XaML Data defining a GUI', Position = 1)]
        [string]$XamlData)
    
    # Add WPF and Windows Forms assemblies
    try {
        Add-Type -AssemblyName PresentationCore, PresentationFramework, WindowsBase, system.windows.forms
    }
    catch {
        Throw 'Failed to load Windows Presentation Framework assemblies.'
    }
    
    # Create an XML Object with the XaML data in it
    [xml]$xmlWPF = $XamlData
    
    # Create the XAML reader using a new XML node reader, UI is the only hard-coded object name here
    Set-Variable -Name XaMLReader -Value @{ 'UI' = ([Windows.Markup.XamlReader]::Load((new-object -TypeName System.Xml.XmlNodeReader -ArgumentList $xmlWPF))) }

    # Create hooks to each named object in the XAML reader
    $Elements = $xmlWPF.SelectNodes('//*[@Name]')
    ForEach ( $Element in $Elements ) {
        $VarName = $Element.Name
        $VarValue = $XaMLReader.UI.FindName($Element.Name)
        $XaMLReader.Add($VarName, $VarValue)
    }

    return $XaMLReader
}


Function New-PopUpWindow () {
    param(
        [string]
        $MessageText = "No Message Supplied")

    # This is the XaML that defines the GUI.
    $WPFXamL = @'
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Popup" Background="#FF0066CC" Foreground="#FFFFFFFF" ResizeMode="NoResize" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" WindowStyle="None" Padding="20" Margin="0">
    <Grid>
        <Button Name="OKButton" Content="OK" HorizontalAlignment="Right" Margin="0,0,30,20" VerticalAlignment="Bottom" Width="75" Background="#FF0066CC" BorderBrush="White" Foreground="White" Padding="8,4"/>
        <TextBlock Name="Message" Margin="100,60,100,80" TextWrapping="Wrap" Text="_CONTENT_" FontSize="36"/>
    </Grid>
</Window>
'@

    # Build Dialog
    $WPFGui = New-WPFDialog -XamlData $WPFXaml
    $WPFGui.Message.Text = $MessageText
    $WPFGui.OKButton.Add_Click( { $WPFGui.UI.Close() })
    $null = $WPFGUI.UI.Dispatcher.InvokeAsync{ $WPFGui.UI.ShowDialog() }.Wait()
}

Example call and result

New-PopUpWindow -MessageText "Hey there, I'm a pretty blue form"

example

vvvv4d
  • 3,881
  • 1
  • 14
  • 18
0

Interested in that too.

Finished with simple WinForms C# app though I'm not in code. It covers every screen with 50% transparent black fullscreen form and then opens modal (OnShown TopMost) blue borderless 678x165 form from the primary screen form. Picked almost same colors and fonts (Segoe UI title + Calibri text and bold buttons) so it is pretty simple and similar phoney. And I've added second red button with some special functionality, what is of course impossible via standard API.

Another way is to know that such a message can be called with Send-RDUserMessage powershell cmdlet from RemoteDesktop module. I opened the module source code and understood that it uses wtsapi32.dll. I stopped pushing that direction because of:

  1. RemoteDesktop module requires admin elevation, maybe wtsapi calls in general requires that too; but I want banner to launch in user context from task scheduler;
  2. Cast Send-RDUserMessage on Windows 10 desktop OS results in small "msg.exe"-like (not sessionmsg.exe) messagebox but I want that banner on Windows 10 workstations too, not only on terminal servers. But workstations use the same banner to say about updates and activation so it is definitely not server unique function.
  3. I am not into coding at all. Someone can try to get along with that dll, maybe WTSSendMessageA and WTSSendMessageW methods which are documented at learn.microsoft.com. Maybe it calls another API because such messages are not only for RD/TS purposes. But I got tired dealing with it.

Still interested in oneliner :)