The mechanism of installing fonts with PowerShell works slightly differently since the Windows 10 1809 feature update.
Previously we could use this one-liner:
(New-Object -ComObject Shell.Application).Namespace(0x14).CopyHere("C:\Build\Font\A39WB_.TTF",0x14);
But since 1809 this installs the font to the per-user location as opposed to the per-machine location! We discussed previously how registering fonts in a per-user context doesn’t work well with App-V and this is the reason why.
What this means is that instead of the font being registered inside c:\windows\fonts, you will now find the font in:
C:\Users\USERNAME\AppData\Local\Microsoft\Windows\Fonts
and a corresponding registry entry in:
HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts
To overcome this, we now use an alternative way of installing fonts to the per-machine location. This involves copying the font to:
C:\Windows\Fonts
and registering an entry inside:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Fonts
To be honest, it’s no real hardship since when installing fonts we need to check for the existence of them first, otherwise we see ugly messages like this that we can’t suppress:
Essentially what we’re describing above is the behaviour of the following 2 options when you right-click on a font file:
Clicking on ‘Install’ installs the font to the per-user location, and clicking ‘Install for all users’ will install it to the per-machine location (more desirable and easier to manage).
The Solution
I ended up writing a couple of PowerShell functions called Install-Font and Uninstall-Font, with a little plagiarising help from the internet!
function Install-Font {
param
(
[System.IO.FileInfo]$fontFile
)
try {
#get font name
$gt = [Windows.Media.GlyphTypeface]::new($fontFile.FullName)
$family = $gt.Win32FamilyNames['en-us']
if ($null -eq $family) { $family = $gt.Win32FamilyNames.Values.Item(0) }
$face = $gt.Win32FaceNames['en-us']
if ($null -eq $face) { $face = $gt.Win32FaceNames.Values.Item(0) }
$fontName = ("$family $face").Trim()
switch ($fontFile.Extension) {
".ttf" {$fontName = "$fontName (TrueType)"}
".otf" {$fontName = "$fontName (OpenType)"}
}
write-host "Installing font: $fontFile with font name '$fontName'"
If (!(Test-Path ("$($env:windir)\Fonts\" + $fontFile.Name))) {
write-host "Copying font: $fontFile"
Copy-Item -Path $fontFile.FullName -Destination ("$($env:windir)\Fonts\" + $fontFile.Name) -Force
} else { write-host "Font already exists: $fontFile" }
If (!(Get-ItemProperty -Name $fontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -ErrorAction SilentlyContinue)) {
write-host "Registering font: $fontFile"
New-ItemProperty -Name $fontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -PropertyType string -Value $fontFile.Name -Force -ErrorAction SilentlyContinue | Out-Null
} else { write-host "Font already registered: $fontFile" }
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($oShell) | out-null
Remove-Variable oShell
} catch {
write-host "Error installing font: $fontFile. " $_.exception.message
}
}
function Uninstall-Font {
param
(
[System.IO.FileInfo]$fontFile
)
try {
#get font name
$gt = [Windows.Media.GlyphTypeface]::new($fontFile.FullName)
$family = $gt.Win32FamilyNames['en-us']
if ($null -eq $family) { $family = $gt.Win32FamilyNames.Values.Item(0) }
$face = $gt.Win32FaceNames['en-us']
if ($null -eq $face) { $face = $gt.Win32FaceNames.Values.Item(0) }
$fontName = ("$family $face").Trim()
switch ($fontFile.Extension) {
".ttf" {$fontName = "$fontName (TrueType)"}
".otf" {$fontName = "$fontName (OpenType)"}
}
write-host "Uninstalling font: $fontFile with font name '$fontName'"
If (Test-Path ("$($env:windir)\Fonts\" + $fontFile.Name)) {
write-host "Removing font: $fontFile"
Remove-Item -Path "$($env:windir)\Fonts\$($fontFile.Name)" -Force
} else { write-host "Font does not exist: $fontFile" }
If (Get-ItemProperty -Name $fontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -ErrorAction SilentlyContinue) {
write-host "Unregistering font: $fontFile"
Remove-ItemProperty -Name $fontName -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Fonts" -Force
} else { write-host "Font not registered: $fontFile" }
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($oShell) | out-null
Remove-Variable oShell
} catch {
write-host "Error uninstalling font: $fontFile. " $_.exception.message
}
}
$currentDirectory = [System.AppDomain]::CurrentDomain.BaseDirectory.TrimEnd('\')
if ($currentDirectory -eq $PSHOME.TrimEnd('\'))
{
$currentDirectory = $PSScriptRoot
}
#Loop through fonts in the same directory as the script and install/uninstall them
foreach ($FontItem in (Get-ChildItem -Path $currentDirectory |
Where-Object {($_.Name -like '*.ttf') -or ($_.Name -like '*.otf') })) {
Install-Font -fontFile $FontItem.FullName
}