Kae Travis

Installing Fonts with PowerShell

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:

Font Already Installed

Essentially what we’re describing above is the behaviour of the following 2 options when you right-click on a font file:

Install Fonts

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  
}  
Installing Fonts with PowerShell
Installing Fonts with PowerShell

Leave a Reply