Extending PowerShell with VBScript and CSharp

Learn how to use other languages to teach PowerShell new tricks, for example how to reliably bring application windows to the front.

For an automation solution, I needed a reliable way of switching another application window to the foreground. PowerShell has no built-in command to do this. A good reason to look at ways to extend PowerShell’s capabilities.

On my quest for a perfect solution, I looked at

  • translating VBScript to PowerShell
  • running C# code inside PowerShell, and
  • loading System Assemblies and borrowing their power.

So in the unlikely event that controlling window focus is not getting you completely excited, next time you stumble across any useful code examples written in VBScript or C#, you’ll know how to integrate them into your PowerShell Scripts.

Ready-to-use function Show-PSOneApplicationWindow

For the impatient reader, download and install the PowerShell module PSOneTools to get a new function named Show-PSOneApplicationWindow:

Install-Module -Name PSOneTools -Scope CurrentUser -MinimumVersion 1.8 -Force

Show-PSOneApplicationWindow brings any application window robustly to the front. Here are some examples:

# Brings current PowerShell window to the top and maximizes the window
Show-PSOneApplicationWindow -Id $pid -Maximize 

# Brings notepad editor window to the top and maximizes it. 
# If there is no notepad application running, an exception is thrown.
# If there is >1 instance of notepad running, only the last instance is affected
Get-Process -Name Notepad | Show-PSOneApplicationWindow -Maximize

# Brings the freshest instance of VSCode to the front (make sure it is running)
# without changing window size:
Get-Process -Name Code | 
  # filter out any instance w/o mainwindowhandle
  Where-Object MainWindowHandle -ne 0 | 
  # freshest first
  Sort-Object -Property StartTime -Descending | 
  # take top
  Select-Object -First 1 |
  Show-PSOneApplicationWindow

The Challenge

It isn’t exactly trivial to reliably bring an application window to the front because there are Windows Policies that may prevent this:

Only a foreground application can switch focus to another window immediately. If a background window sets focus to another window, it will get foreground focus only if the foreground window was idle for some time. Else, Windows flashes the taskbar button for the window and leaves it up to the user to decide when the window gets the focus.

And that’s perfectly ok: you wouldn’t like working on a script either when suddenly in the middle of your work some other window pops in front.

But in the world of Automation, this often is a completely different ball game, and switching focus immediately may be crucial. Which leaves you with google for an afternoon or two. Most likely, when you do find suggestions eventually, they are often written in languages other than PowerShell.

So let’s check out how code from other languages can be incorporated into PowerShell.

VBScript to the Rescue

While googling, you may find old VBScript solutions similar to this one:

processId = 6736
Set shell = CreateObject("WScript.Shell")
shell.AppActivate processId

It uses CreateObject to access a COM Component. Most of the magic performed by VBScript is really done by such COM Components. The component WScript.Shell provides a range of automation methods, and AppActivate is one of them, takes a process id and aims to bring the application window to the front.

Translating to PowerShell

Translating VBScript to PowerShell is not hard at all: use New-Object -ComObject ... in place of CreateObject. That’s basically it. The object models provided by the COM Components stay the same, so you just need to adjust to the flavor of language constructs such as variables and loops.

# get the process ID of the first instance of the ISE editor
# (make sure there is at least one running, or change the process name)
$id = Get-Process -Name powershell_ISE | Select-Object -ExpandProperty Id -First 1

# access the COM component
$shell = New-Object -ComObject WScript.Shell

# call "AppActivate"
$shell.AppActivate($id)

Looking at Results

What’s good: PowerShell happily runs the translated code.

What’s not: the solution doesn’t always work.

AppActivate() returns a boolean value which can be $true (the target window received focus) or $false (it did not work). While playing with AppActivate(), you can see the effects of the Windows Policies: depending on the focus state of the caller, Windows permits or denies the focus switch.

As you have seen, translating VBScript to PowerShell isn’t hard but that doesn’t guarantee that the original VBScript Code was performing well in the first place and does what you had hoped.

C# to the Rescue

Often when you google things, you come across C# code examples. C# is the language used to program and compile most modern Windows applications. PowerShell is an example and was written in C#, too.

C# code can be very complex but has access to the lowest levels of Windows system calls, the API (Application Programming Interface).

Example of C# code that circumvents Windows Policies to robustly focus a window

The C# code below defines a method named FocusWindow() that can circumvent the Windows Policy and make sure any application window receives focus immediately. It’s kind of lengthy but well commented:

  • If there is no window currently in the foreground, or the foreground window belongs to the caller, then a focus switch is no problem and immediately performed
  • If there is a foreground window owned by someone else, AttachThreadInput() temporarily attaches itself to the blocking thread and switches focus on behalf of the foreground thread.
  • If that fails, the inactivity timeout is set to 0: when a foreground window was inactive for at least the time defined by this timeout, someone else can “steal” the focus. So by setting the timeout to 0, anyone can steal the focus anytime.
using System;
// we need access to this assembly in order to access
// low-level API methods:
using System.Runtime.InteropServices;

// create own namespace...
namespace API
{
    // ...and own PUBLIC class. Only things that are declared PUBLIC
    // will be visible for PowerShell:
    public class FocusWindow
    {
        // declare the signatures of Windows API methods:
        [DllImport("User32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach);

        [DllImport("User32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("User32.dll")]
        private static extern IntPtr GetWindowThreadProcessId(IntPtr hwnd, IntPtr lpdwProcessId);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32")]
        private static extern int BringWindowToTop(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);

        // we also need a bunch of constants because their names are easier
        // to remember than their values:
        private const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
        private const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
        private const int SPIF_SENDCHANGE = 0x2;

        // these constants describe the way how a window can be sized
        private const int SW_HIDE = 0;
        private const int SW_SHOWNORMAL = 1;
        private const int SW_NORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;
        private const int SW_SHOWMAXIMIZED = 3;
        private const int SW_MAXIMIZE = 3;
        private const int SW_SHOWNOACTIVATE = 4;
        private const int SW_SHOW = 5;
        private const int SW_MINIMIZE = 6;
        private const int SW_SHOWMINNOACTIVE = 7;
        private const int SW_SHOWNA = 8;
        private const int SW_RESTORE = 9;
        private const int SW_SHOWDEFAULT = 10;
        private const int SW_MAX = 10;

        // we create our own PUBLIC (and static) method, similar to a 
        // PowerShell function. "Static" means: the method will later be accessible
        // directly via the type: [API.FocusWindow]::Focus()
        public static void Focus(IntPtr windowHandle)
        {
            // we cannot switch a window to the foreground if WE are NOT in the foreground
            // so WHO IS in the foreground? Let's get the foreground window and its thread ID:
            IntPtr blockingThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
            // let's also get the thread id from our own process:
            IntPtr ownThread = GetWindowThreadProcessId(windowHandle, IntPtr.Zero);

            // if WE happen to be the FOREGROUND app ourselves, all is good, and we can switch
            // the other window to the foreground. Same applies when there is currently no foreground window.
            if (blockingThread == ownThread || blockingThread == IntPtr.Zero)
            {
                // set the foreground window...
                BringWindowToTop(windowHandle);
                SetForegroundWindow(windowHandle);
                // ...and show the window maximized. Use any of the constants above to show the window
                // in any way you need:
                ShowWindow(windowHandle, SW_MAXIMIZE);
            }
            else
            {
                // the tricky part starts when WE are NOT currently the foreground window
                // In this case, we simply attach ourselves to the blocking window and act on its behalf:
                if (AttachThreadInput(ownThread, blockingThread, true))
                {
                    // if that worked, we can safely switch the windows on behalf of the current
                    // foreground window into the foreground
                    BringWindowToTop(windowHandle);
                    SetForegroundWindow(windowHandle);
                    ShowWindow(windowHandle, SW_MAXIMIZE);
                    // at the end, we restore the thread input back to what it was before:
                    AttachThreadInput(ownThread, blockingThread, false);
                }
            }

            // if for any reason our window is STILL not the foreground window...
            if (GetForegroundWindow() != windowHandle)
            {
                // ...we temporarily TURN OFF the blocking mechanism:

                // first we read the current timeout which is the time a foreground window can be inactive
                // before other windows can get focus:
                IntPtr Timeout = IntPtr.Zero;
                SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, Timeout, 0);

                // next, we change this timeout to 0, effectively turning off the feature that normally
                // prevents background apps to interfere with the foreground app:
                SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, SPIF_SENDCHANGE);

                // now we can safely do the focus switch:
                BringWindowToTop(windowHandle);
                SetForegroundWindow(windowHandle);
                ShowWindow(windowHandle, SW_MAXIMIZE);

                // at the end, we restore the timeout because after all, it does make sense:
                SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Timeout, SPIF_SENDCHANGE);
            }
        }
    }
}

FocusWindow() takes the handle of the window that you want to send to the front. Getting a Window Handle for a process is easy:

# get the main window handle for a notepad editor window
# make sure you have opened a notepad editor, or changed the process name
$process = Get-Process -Name notepad | Select-Object -First 1

$handle = $process.MainWindowHandle

if ($handle)
{
	Write-Host "Window Handle: $handle"
}
else
{
	Write-Host "Window may be minimized" -Foreground Red
}

If the returned handle is [IntPtr]::Zero (or null), then the target window may currently be hidden or otherwise invisible.

Translating to PowerShell

Fortunately, there is no need to translate the huge block of C# code. Instead, Add-Type can compile it in memory and make available anything that is marked public in the code.

That’s the important point: the C# code defines a public class named FocusWindow, and inside of it, defines a public static method called Focus. So PowerShell can access this method. It cannot access any other methods, i.e. the API methods such as GetForegroundWindow().

One common pitfall by incorporating C# code is to not look at these accessors. If you copy&paste code that declares all members as private (which is the default state), then PowerShell won’t be able to use anything from that code.

Essentially, all you need to do is send the entire C# code to Add-Type:

$code = @'
using System;
using System.Runtime.InteropServices;

namespace API
{

    public class FocusWindow
    {
        [DllImport("User32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach);


        [DllImport("User32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("User32.dll")]
        private static extern IntPtr GetWindowThreadProcessId(IntPtr hwnd, IntPtr lpdwProcessId);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32")]
        private static extern int BringWindowToTop(IntPtr hWnd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);

        private const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
        private const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
        private const int SPIF_SENDCHANGE = 0x2;

        private const int SW_HIDE = 0;
        private const int SW_SHOWNORMAL = 1;
        private const int SW_NORMAL = 1;
        private const int SW_SHOWMINIMIZED = 2;
        private const int SW_SHOWMAXIMIZED = 3;
        private const int SW_MAXIMIZE = 3;
        private const int SW_SHOWNOACTIVATE = 4;
        private const int SW_SHOW = 5;
        private const int SW_MINIMIZE = 6;
        private const int SW_SHOWMINNOACTIVE = 7;
        private const int SW_SHOWNA = 8;
        private const int SW_RESTORE = 9;
        private const int SW_SHOWDEFAULT = 10;
        private const int SW_MAX = 10;

        public static void Focus(IntPtr windowHandle)
        {
            IntPtr blockingThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
            IntPtr ownThread = GetWindowThreadProcessId(windowHandle, IntPtr.Zero);

            if (blockingThread == ownThread || blockingThread == IntPtr.Zero)
            {
                SetForegroundWindow(windowHandle);
                ShowWindow(windowHandle, 3);
            }
            else
            {
                if (AttachThreadInput(ownThread, blockingThread, true))
                {
                    BringWindowToTop(windowHandle);
                    SetForegroundWindow(windowHandle);
                    ShowWindow(windowHandle, SW_MAXIMIZE);
                    AttachThreadInput(ownThread, blockingThread, false);
                }
            }

            if (GetForegroundWindow() != windowHandle)
            {
                IntPtr Timeout = IntPtr.Zero;
                SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, Timeout, 0);
                SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, SPIF_SENDCHANGE);
                BringWindowToTop(windowHandle);
                SetForegroundWindow(windowHandle);
                ShowWindow(windowHandle, SW_MAXIMIZE);
                SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Timeout, SPIF_SENDCHANGE);
            }
        }
    }
}
'@

# remove -PassThru in production. It is used only to
# expose the added types:
Add-Type -PassThru -TypeDefinition $code

Do not indent the line starting with '@": this Here String delimiter must start at the beginning of the line!

When you specify the parameter -PassThru, Add-Type returns the added types. The result should look like this:

IsPublic IsSerial Name                                     BaseType                                     
-------- -------- ----                                     --------                                     
True     False    FocusWindow                              System.Object

So there was one new type called FocusWindow added to PowerShell. Unfortunately, Add-Type only shows the short name of the type. You’ll be needing the full name. Run this instead:

Add-Type -PassThru -TypeDefinition $code |
  Where-Object IsPublic |
  Select-Object -Property FullName

Now the result looks like this:

FullName       
--------       
API.FocusWindow

There is one public type called API.FocusName (C# code can define many types so this list could be much longer).

Looking at the Results

Switching any application window reliably to the foreground is now as simple as calling the static method Focus() provided by the type API.FocusWindow.

The code below brings the first instance of the notepad editor to the front (make sure at least one is running, or change the process name):

# get the main window handle for the process
# you want to switch to the foreground:

# in this example, the first instance of notepad is used
# (make sure notepad runs)
$process = Get-Process -Name notepad -ErrorAction SilentlyContinue | 
    Select-Object -First 1

$mainWindowHandle = $process.MainWindowHandle

if (!$mainWindowHandle)
{
	Write-Host "Window may be minimized, or process may not be running" -Foreground Red
}

# focus application window (and maximize it)
[API.FocusWindow]::Focus($mainWindowHandle)

This works reliably now and is circumventing any Windows Policy. For automation purposes that’s great. To tease your colleagues, it is not.

Fine-Tuning C# Code

You’ll soon discover that the C# Code brings the application window to the front and maximizes the window.

So you may want to fine-tune the original C# code a bit. The maximizing is done by this line:

ShowWindow(windowHandle, SW_MAXIMIZE);

Either remove the line altogether, or change the constant to some of the other options (see source code).

Pitfall: No Changes Allowed

Once you changed the C# source code and run your script again, you’ll inevitably run into an exception. Types added by Add-Type cannot be overwritten or changed.

You must restart PowerShell and compile in a fresh PowerShell instance. In the ISE editor, you can also open a new PowerShell Tab, and in VSCode, you can restart the underlying PowerShell engine.

Another reason for exceptions may be harder to solve: fiddling with C# source code is error-prone because C# is a case-sensitive language and has plenty of little rules, i.e. all lines must end with ;. If you work on the code blindfolded in a vanilla editor, it is almost certain that you miss something and end up with syntax errors.

The best way to edit C# source code is to use an editor with specific C# support that warns you when something is amiss.

Either use a full-blown development environment like Visual Studio, or grab the light-weight cross-platform editor VSCode and install support for C#.

UIAutomationClient to the Rescue

Sometimes, what you need is already part of Windows. The UIAutomationClient is a framework of methods to help improve access to application for impaired users. This framework also contains methods to control focus.

Accessing UIAutomationClient from PowerShell Code

UIAutomationClient is a type, just like API.FocusWindow. It already exists, so there is no source code required. To add the type to PowerShell, use Add-Type again, and submit the name of the Assembly you want to load.

An Assembly is basically a file-based container for types. In the filesystem, these containers surface as DLL files.

Add-Type -AssemblyName UIAutomationClient -PassThru |
  Where-Object isPublic |
  Select-Object -Property FullName

Once you have access to UIAutomationClient, you can access application windows and put them in front, among other things. Below code brings an instance of the PowerShell ISE editor to the front (make sure you run one):

# add UIAutomationClient types
Add-Type -AssemblyName UIAutomationClient 

# in this example, the first instance of the ISE editor is used
# (make sure ISE runs)
$mainWindowHandle = Get-Process -Name powershell_ISE -ErrorAction SilentlyContinue | 
    Select-Object -First 1 -ExpandProperty MainWindowHandle

if (!$mainWindowHandle)
{
	Write-Host "Window may be minimized, or process may not be running" -Foreground Red
}

# get access to the application window...
$element = [System.Windows.Automation.AutomationElement]::FromHandle($mainWindowHandle)
# ...and focus it:
$element.SetFocus()

Looking at the Results

This approach works perfectly and is small and simple. Five stars.

Except. You may be wondering why I am using the PowerShell ISE editor in this last example, and not the notepad editor like in the examples before.

Simple answer: because it turned out UIAutomationClient cannot focus the notepad editor window. It can control only applications that actively subscribe to the UIAutomationClient framework can be controlled.

Fortunately, most modern top-level applications such as Word or ISE do, but simple little tools and applications such as notepad do not.

Conclusions

There are two conclusions for me, one related to the original challenge, and one about PowerShell in general:

Focusing Application Windows

If you need a robust way to focus any application window, then the C# approach used to circumvent Windows Policies is the best route to take.

If the application that you need to control is a UIAutomationClient, then focusing its window via UI Automation is a much cleaner and simpler way. Whether you can use this route depends entirely on the type of application and its UI Automation support.

Extending PowerShell

There are three primary ways to extend PowerShell functionality on code level:

  • COM Components/VBScript: Thanks to New-Object -ComObject ..., PowerShell can utilize most of the COM components found in VBScript code. You even get limited intellisense once you instantiated the COM component.

    However, most COM Components stem from a time before the year 2001 when .NET Framework surfaced, so chances are there are much better alternatives available by now.

  • System Assemblies: Windows ships with thousands of assemblies, and Add-Type can load them. This way, you can immediately access all public types exposed by these assemblies.

    This of course is only the technical aspect. In reality, you need to know about these assemblies, the types, and what you can do with them. That’s why there will be follow-up articles every now and then on this.

  • C# Code: PowerShell is written in C#, so it can compile C# code on the fly, and you can use C# to define classes and types that are immediately accessible from within your PowerShell Code.

    Add-Type cannot change and edit types that you have added, so developing C# code in PowerShell is very cumbersome. That’s why you should use a dev environment that fully supports C# to develop the C# code for your challenges. Once it runs the way you want it to run, copy it into your PowerShell scripts and compile the code on-the-fly: Add-Type -TypeDefinition "c# code".

    You can also compile your C# code into a DLL file and ship this file with your script: Add-Type -Path c:\path\to\library.dll.

PowerShell Conference EU 2020

If you enjoy stuff like this, make sure you have PowerShell Conference EU 2020 on your plate, either as delegate or as a speaker. See you there!