Powershell MessageLoop error when calling shell.Application com object

I’m trying to automate disabling fonts via shell.Application invokeVerb('hide')
I believe the issue is related to -STA and -MTA and passing a reference to MesageLoop to shell.Application object .

I’ve found other font managers with powershell but they all do direct registry manipulation and I’m trying to avoid that.

Steps to Reproduce

  1. execute pwsh -STA or pwsh -MTA using ⊞-R or existing powershell terminal
  2. run the following code
$Name = "Brush Script MT Italic"
$sh = New-Object -ComObject "Shell.Application"
$sh.Namespace("C:\Windows\Fonts").ParseName($Name) | ForEach-Object { $_.InvokeVerb("hide") }
  1. open the fonts control panel with ⊞-R → fonts

Expected Results

The font is hidden

ACTUAL RESULTS

no change to the font.
sometimes this error message is shown

 [24136:ShellIpcClient] message_loop.cc:132:Run Run called on MessageLoop that's already been Quit!

This is not a powershell error. How are you executing this?

i believe it’s a error from shell.Application (windows explorer) . I’m running the code sample in Powershell. I’ve tested -STA and -MTA

You say you try -STA and -MTA but do not show how you try this. Please provide details on how you are actually running these commands.

default (STA)

pwsh -STA

MTA mode

pwsh -MTA

I am not a mind reader and I cannot see your screen. Where do you type this? In the start menu? Either way, I cannot reproduce your error in any version of powershell. I feel there is a critical piece of information missing here. The error makes me believe you are running this through some type of platform.

Can you attempt to just simply click start, click on powershell, and run these commands one at a time.

$Name = "Brush Script MT Italic"
$sh = New-Object -ComObject "Shell.Application"
$font = $sh.Namespace("C:\Windows\Fonts").ParseName($Name) 
$font | Get-Member

You should see something like this

   TypeName: System.__ComObject#{edc817aa-92b8-11d1-b075-00c04fc33aa5}

Name             MemberType Definition
----             ---------- ----------
ExtendedProperty Method     Variant ExtendedProperty (string bstrPropName)
InvokeVerb       Method     void InvokeVerb (Variant vVerb)
InvokeVerbEx     Method     void InvokeVerbEx (Variant vVerb, Variant vArgs)
Verbs            Method     FolderItemVerbs Verbs ()
Application      Property   IDispatch Application () {get}
GetFolder        Property   IDispatch GetFolder () {get}
GetLink          Property   IDispatch GetLink () {get}
IsBrowsable      Property   bool IsBrowsable () {get}
IsFileSystem     Property   bool IsFileSystem () {get}
IsFolder         Property   bool IsFolder () {get}
IsLink           Property   bool IsLink () {get}
ModifyDate       Property   Date ModifyDate () {get} {set}
Name             Property   string Name () {get} {set}
Parent           Property   IDispatch Parent () {get}
Path             Property   string Path () {get}
Size             Property   int Size () {get}
Type             Property   string Type () {get}

If so, run this command

$font.InvokeVerb("hide")

I am not a mind reader and I cannot see your screen. Where do you type this? In the start menu?

you can run this in start menu or powershell

$font.InvokeVerb(“hide”)

this has the same result

PowerShell 7.4.1
It’s possible i have a different log level which is showing this error. The error is coming from the com+ shell.application object. I’ll see if it’s in Windows Event log to help you reproduce.

are you seeing the font disabled in your fonts control panel? you can open fonts control panel with start → run → “fonts”

in further research the “message_loop.cc” message may be a side effect of another shell extension installed on my machine. But the invokeVerb("hide") result is still failing. does it work for you?

It doesn’t error, so I assume it worked. I’m not familiar with hiding fonts.

.Verbs() is helpful to see what shell verbs are available. Can you try both hide and &Hide ?

$font = $sh.Namespace("C:\Windows\Fonts").ParseName("Playbill Regular")
❯ $font.Verbs()    
2024-03-29T17:36:42.529ZE [27784:ShellIpcClient] message_loop.cc:132:Run Run called on MessageLoop that's already been Quit!

Application Parent Name
----------- ------ ----
                   Pre&view
                   &Print
                   &Hide
                   Edit with &Notepad++
                   Add to &Favorites
                   &Copy
                   &Delete
                   P&roperties
❯ $font.invokeVerb("hide")
2024-03-29T17:37:04.253ZE [18836:ShellIpcClient] message_loop.cc:132:Run Run called on MessageLoop that's already been Quit!

❯ $font.invokeVerb("&Hide")
2024-03-29T17:37:10.251ZE [16028:ShellIpcClient] message_loop.cc:132:Run Run called on MessageLoop that's already been Quit!

most likely it’s failing. the font status is shown in the control panel. The result is shown in “show / hide” column

Tony,

Apologies, but this question is really out of scope for this forum. Based on reading, I’m seeing nothing that suggests this truly is a PS question. It’s more like you’re using PS as a ‘path’ to get what you want accomplished, but the underlying windows api is throwing the error on your end, something Doug or I an’t replicate.

  1. $font.InvokeVerb() makes the font ‘open’ for me when ran in PS, which suggests that the method without any parameters is working (likely makes the call using a default parameter). I’m thinking this default method corresponds with ‘Preview’ on the right click context pane.
  2. I was not able to retrieve any errors using $font.InvokeVerb('hide'). I also don’t think I see what it did either. I presume you’re trying to ‘hide it’ from the GUI in C:\Windows\Fonts. I don’t have experience using that method, but are you sure it accepts ‘hide’ as a parameter or verb? $font.Verbs() seems to be a method designed to to get the available list of verbs associated with the item. However, testing, I can’t seem to get any of them to work. It’s worth pointing out that i see &Hide in the list, not Hide. That said I couldn’t get that to ‘hide’ the font’, and I’d assume a Explorer restart isn’t necessary.

I believe it could be of use to developers who are integrating powershell with com+ objects and shell.application which is a common automation API

the error message I’m having may not be deterministic but it may be indicative of the underlying error communicating with shell.application

thank you for helping reproduce these results. i’m seeing this as well

I’ve updated the post to be clearer on the outcomes

EDIT
@krzydoug thank you for DoIt() method that worked. ! (i’m blocked from replies)

The way I got this to work was to not use InvokeVerb but to capture the verb and run the DoIt() method. I’ve written a few functions to make this easier.

Function Get-Font {
    Param(
        [parameter(Mandatory)]$FontName
    )
    $shell = New-Object -ComObject "Shell.Application"
    $namespace = $shell.Namespace("C:\Windows\Fonts")
    $font = $namespace.ParseName($FontName)

    [PSCustomObject]@{
        Name   = $font.Name
        Path   = $font.Path
        Hidden = $namespace.GetDetailsOf($font,2) -eq 'Hide'
    }
}

Function Hide-Font {
    Param(
        [parameter(Mandatory)]$FontName
    )

    $font = Get-Font $FontName

    if($font.hidden){
        Write-Verbose "Font '$FontName' is already set to 'Hide'"
    }
    else{
        Write-Verbose "Setting font '$FontName' to 'Hide'"
        $shell = New-Object -ComObject "Shell.Application"
        $namespace = $shell.Namespace("C:\Windows\Fonts")
        $font = $namespace.ParseName($FontName)
        $hideverb = $font.Verbs() | Where-Object Name -like '*hide*'
        $hideverb.DoIt()
    }
}

Function Show-Font {
    Param(
        [parameter(Mandatory)]$FontName
    )

    $font = Get-Font $FontName

    if($font.hidden){
        Write-Verbose "Setting font '$FontName' to 'Show'"
        $shell = New-Object -ComObject "Shell.Application"
        $namespace = $shell.Namespace("C:\Windows\Fonts")
        $font = $namespace.ParseName($FontName)
        $showverb = $font.Verbs() | Where-Object Name -like '*show*'
        $showverb.DoIt()
    }
    else{
        Write-Verbose "Font '$FontName' is already set to 'Show'"
    }
}

Now you can simply call Show-Font or Hide-Font

$Name = "Brush Script MT Italic"
Hide-Font $Name

Note the functions do not create output. You could alter them to return true or false, which I would recommend using $erroractionpreference = 'Stop' and a try/catch if you wanted to go that route. If you add -Verbose you can see more info

$Name = "Brush Script MT Italic"
Hide-Font $Name -Verbose
Show-Font $Name -Verbose
1 Like