0

I am trying to refine this to get only the Title and Type attributes for font files. I have used that function to verify that my target file has those attributes, and I have this

$shell = new-object -com shell.application
$folder = $shell.namespace("\\px\Rollouts\Misc\Fonts\Arial")
$item = $folder.Items().Item('arial.ttf')
$item.ExtendedProperty('Title')
$item.ExtendedProperty('Type')

which correctly returns the Type, but not the Title. So, I have two questions.

1: What is the difference between those two properties, that one works and one doesn't?

and

2: Is there a better, native PowerShell or PS with .NET way too get at this info, that doesn't require putting to COM? My searching shows examples of using the COM kludge even in C#, which just seems like madness.

EDIT: I can use this

$folder.getDetailsOf($item, 21)
$folder.getDetailsOf($item, 191)

but I wonder if those numbers really are universal. My first thought was that I would prefer to use the property name, but then I though, is the name the same on a French machine? Ah, nothing's easy. :)

EDIT 2: Nope, not universal. That 191 reference for the font type is 182 in Windows 7. Ugh.

EDIT 3: OK, I have this, and it works on Windows 7 & Windows 10.

$shell = new-object -com shell.application
$folder = $shell.namespace("\\px\Rollouts\Misc\Fonts\Arial")
$item = $folder.Items().Item('arial.ttf')
foreach ($i in 0..266) {
    if ($folder.getDetailsOf($folder.items, $i) -eq 'Title') {
        $title = "Title: $($folder.getDetailsOf($item, $i))"
    }
    if ($folder.getDetailsOf($folder.items, $i) -eq 'Type') {
        $type = "Type: $($folder.getDetailsOf($item, $i))"
    }
    if ($title -and $type) {
        break
    }
}
$title 
$type

Performance is acceptable, so if someone can verify that this will work on machines with alternate languages I guess I have A solution. But would still love to hear about a GOOD solution.

EDIT 4: OK, this is getting ever more complicated. The [Windows.Media.GlyphTypeface] approach was looking good for Arial. But then I looked at You Gothic (YuGothM.ttc), which is in the registry as Yu Gothic Medium & Yu Gothic UI Regular (TrueType), and that string can't even be constructed from data in the file. I am a bit at a loss for how one is to start from a file and end up with a font that is installed for all users exactly the same way it would have been before Microsoft @$%#$ed this up. I mean, I CAN do it now for English, but I really want universal support. What's even more odd is the fact that Yu Gothic isn't even available in Wordpad to test if getting the registry property name right is even important. But one that is is sitka.ttc, which uses Sitka Small & Sitka Text & Sitka Subheading & Sitka Heading & Sitka Display & Sitka Banner (TrueType). I changed that to just Sitka Small, which is available via the Win32FamilyNames property, and that seems to work, the others like Sitka Display and Sitka Heading are still available in Wordpad. But does that suggest that the name of the property in the registry is somewhat arbitrary? And it still begs the question, when installing with right click Install how is Windows determining what to call this stuff? It has to be info in the file, right?

EDIT 5: Based on @filimonic answer I now have this, and it is SOOOO close. Odd thing is that with Segoe UI Light the weight is reported as 350. I guess I'll need to test the weight and not use it if it's an integer. Likely I need to test every font and see what they all report, and hope integer is a viable filter.

$file = '\\Localhost\c$\Windows\Fonts\seguisli.ttf'
$glyph = [System.Windows.Media.GlyphTypeface]::new([uri]::new($file))
if ($null -eq ($familyNames = $glyph.Win32FamilyNames['en-us'])) {
    $familyNames = $gi.Win32FamilyNames.Values.Item(0)
}
$weight = $glyph.Weight
$style = $glyph.Style

$familyNames
$weight
$style
Gordon
  • 6,257
  • 6
  • 36
  • 89
  • Perhaps `[Windows.Media.GlyphTypeface]::new('\\Path\To\arial.ttf')` gives the properties you need? – Theo Aug 10 '20 at 20:25
  • @Theo, it's partial, in that the FamilyNames seem to match up with Title, but there is no Type information. And when manually adding fonts as needed to get around Microsoft @%#$ing up installing fonts and redirecting to current user, I need to format the property name in the registry as Arial (TrueType), so I want to extract the title/face name and type direct from the file if possible. – Gordon Aug 10 '20 at 20:40
  • Type information as you get it `$item.ExtendedProperty('Type')` relies on file extension only. You can try renaming *any* `.txt` file to `.ttf` and you'll get `TrueType font file`. It's a trash information, IMO. Rely on `GlyphTypeface` as @Theo said. If you want, keep file extension `$ext = [System.IO.Path]::GetExtension($path)` – filimonic Aug 10 '20 at 22:03
  • @filimonic OK, I started down this path, but now I am running into a new conundrum, see EDIT #4. – Gordon Aug 11 '20 at 07:16
  • @Gordon, what is your final result? YOu're start talking abount installations etc. If yopu want to list INSTALLED fonts, it's easier than to list folder. I mean maybe you're on comletely wrong way – filimonic Aug 11 '20 at 07:23
  • @filimonic, my ultimate goal is to be able to start from a font file, see if that font is already installed, and if not install it for all users. But to do that now, after Microsoft broke things, requires directly iterating over the registry, and adding the new font property if it isn't installed yet. And getting the correct name of the property is proving to be the difficult part. – Gordon Aug 11 '20 at 07:37
  • Why just not install fonts to everyone, ignoring the fact it is already installed? Just take software like AdvancedInstaller, create simple project (it's free), create MSI installer with font file (font files requre ~register file~ checkbox) and run it through group policies. That's all. Every new computer will automatically install fonts. Font pack is registered in Add\Remove menu for inventarization software. Don't forget to save project, so it's easier to make upgrades when a new font will appear. // I use Adv.Installer with over 14k computers to install fionts using generated MSIs. – filimonic Aug 11 '20 at 07:56
  • @filimonic The thing is, it needs to be done over the life of the machine, not just when the machine is first provisioned. And it needs to work in small architecture or engineering firms (sometimes as few as 10 people, none with real IT experience) where creating MSIs and using Group Policy is not part of the skill set. It used to work fine using `$fontFolder = $(New-Object -ComObject:Shell.Application).Namespace(0x14)` and `.CopyHere` but then MS went and made that a user only behavior, which is a HUGE problem in design firms. – Gordon Aug 11 '20 at 08:41
  • I'd recommend to look at this https://stackoverflow.com/q/21986744/1936966 about programmatically installing fonts. And I'd recommend not to care if font's already installed in some *non-programmatical way*? just install it right. – filimonic Aug 11 '20 at 08:54
  • @filimonic That is indeed what I want to do, but "install it right" seems to be the quarry. Given `key.SetValue("My Font Description", "fontname.tff");` as mentioned in that link, establishing what `My Font Description` needs to be is the issue. It seems like that info must be in the file somewhere. Hopefully digging some more in that link will lead me to it. :) – Gordon Aug 11 '20 at 09:02
  • $fontCol = [System.Drawing.Text.PrivateFontCollection]::new(); $fontCol.AddFontFile($file); $fontCol.Families.Name; $fontCol.Dispose(); # Works with .ttc also. – filimonic Aug 11 '20 at 09:09
  • @filimonic Interesting. So `$fontCol.Families` provides the different names. But `Sitka Small & Sitka Text & Sitka Subheading & Sitka Heading & Sitka Display & Sitka Banner (TrueType)` is in an arbitrary order. Not alphabetical like the data is provided. But it does seem like the standard is just to list all the names separated with `&`, so that's a start. I guess as long as I only have one property that references the actual font file, and that property name includes all the names extracted like this, being in a different order won't matter? – Gordon Aug 11 '20 at 09:19
  • Seems the order does not have any meaning. Just check that all of fonts in particular file are known by system. BTW, there are some fonts that have _versions_, something is getting updated. But you will not be able to handle it I think. – filimonic Aug 11 '20 at 09:25
  • @filimonic OK! That gives me something to implement and test the heck out of. And certainly the GOOD solution I was initially looking for, with no COM kludge. – Gordon Aug 11 '20 at 09:31
  • @filimonic DRAT. Almost there. So all of that looks good for basic Sitka, but there are also bold, italic and bold-italic options. Bold uses the file `SitkaB.ttc` and the property is called `Sitka Small Bold & Sitka Text Bold & Sitka Subheading Bold & Sitka Heading Bold & Sitka Display Bold & Sitka Banner Bold (TrueType)`, but the results of the first code example are the same for both files. And I can't just assume those three variations, as Arial has a Black and Calibri has a Light and Light Italic. though at least with those two other Calibri options they are in the file. – Gordon Aug 11 '20 at 09:42
  • And it's not just Sitka. Trebuchet & Verdana also have 4 variations, with 4 TTF files, but `$fontCol.Families.Name` is identical for all 4 files. – Gordon Aug 11 '20 at 09:47
  • Seems Windows somehow handles this. Uopdated anser, see this. – filimonic Aug 11 '20 at 10:11

1 Answers1

1

To get font names from file:

$file = 'C:\...\File.ttc'
$fontCol = [System.Drawing.Text.PrivateFontCollection]::new()
$fontCol.AddFontFile($file)
$fontListInFile = [String[]]$fontCol.Families.Name
$fontCol.Dispose()

To get installed font names:

$fontCol = [System.Drawing.Text.InstalledFontCollection]::new()
$fontListInstalled = [String[]]$fontCol.Families.Name
$fontCol.Dispose()

Alternatively with extended options:

$files = @( Get-ChildItem -Path 'C:\Windows\Fonts' -File  ) | 
    Where-Object {$_.Name -notlike '*.fon'} |
    Where-Object {$_.Name -notlike '*.CompositeFont'} |
    Where-Object {$_.Name -notlike '*.ini'}

$fonts = @()
forEach ($f in $files)
{
    try 
    {
        $gi = [System.Windows.Media.GlyphTypeface]::new([uri]::new($f.FullName))

        $fam = $gi.Win32FamilyNames['en-us']
        if ($null -eq $fam) { $fam = $gi.Win32FamilyNames.Values.Item(0) }
        $w = $gi.Weight
        $s = $gi.Style


        $fonts += @([PSCustomObject]@{
            'File' = $f.FullName
            'FontFamily' = $fam
            'Weight' = $w
            'Style' = $s
            'FontFullName' = "$($fam)#$($s)#$($w)"})
    }
    catch
    {
    Write-Warning -Message "Error reading $($f.FullName) : $($_.Exception.Message)"
    }
}
filimonic
  • 3,988
  • 2
  • 19
  • 26
  • Quick question as I try to implement this. Where do I find/load `[System.Windows.Media.GlyphTypeface]`? I found a reference to PS 6, but I am limited to PS 5.1, so hopefully I am just missing how to get access to this type. – Gordon Aug 13 '20 at 16:10
  • `[System.Windows.Media.GlyphTypeface]` is windows-only DLL comes in `PresentationCore.dll` file. – filimonic Aug 21 '20 at 23:32