Installing a new font in Windows

This can’t be difficult, right? It is just drag and drop. Get your newest shiny font, open c:\windows\Fonts and drop it there. This should also be very easy to automate then. Well, ouch! There would be no post then:)

Gathering the pieces

I started with simplest possible solution - I tried copying file to C:\Windows\Fonts. I was requested by providing administrative credentials (I’m not running with scissors :) ). Good. Confirmed that drag and drop is working as expected. That won’t be a viable solution for 20+ workers uploading different fonts daily. Oh, and they don’t have admin rights either!

I tried copying the file with Copy-Item -Credential. It worked but font was not registered. I’ve created proper registry entry in HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts but that didn’t do the trick either.

Sure enough the font was visible in Fonts folder but was not accesible by any application.

Let the hunt begin

I’ve used some google-fu here and there and it seems that:

is enough.

The road to victory seemed cleared. Just one last monster to slay - to add proper registry key I needed the TRUE font name, not the file name. How to retrieve additional properties of a font file (those from ‘details’ tab)?

Font

Those properties are not accessible by any built-in PowerShell cmdlet. I could either use another Shell.Application object and retrieve the Title (or iterate through the 288 properties… Mick Pletcher has a nice article about it, or I could use System.Drawing assembly for that.

Got the meat, let’s plan

I had all the information I needed to get the cooking planning going. First function flow looked like this:

Flow1

I wanted to be able to either add one file or point to a directory with fonts, but not both. What about some failsafes? I wanted to be sure that only appropriate file types will be selected and copied to C:\Windows\Fonts folder. Also I wanted to copy the file only if it wasn’t there already. Imagine a user having a folder with bunch of fonts and adding new ones there, while keeping the old one “just for reference”.

So the final flow is something like:

Flow1

Let the coding begin

Let’s start the fun part. To accept only one parameter I’ve used ParameterSetName in Param() block for each parameter. Parameter $Path accepts only containers (folders) and is labeled 'Directory'. Parameter $FontFile accepts only files and is labeled 'File'. To have ‘Directory’ set as default I added an additional declaration in CmdletBinding() section

The Begin{} block initializes variables. To create a Shell.Application with proper flags ReadOnly variable $Fonts is created with special ID (0x14). I’m also using copyFlag options 4+16 which means ‘Do not display a progress dialog’ (which it still does!) and ‘Respond with “yes to all” for any dialog box that is displayed’ to avoid bothering popups during the process.

Next interesting thing is getting font files whether directory or file was provided. I’ll need object with properties (Full Path, Name) so I’ll Get-ChildItem through, but only if file has allowed extension:

While in the foreach-object loop and not-yet-registered font condition, I’ll retrieve 'Details' properties from the font file with System.Drawing` type object:

Still in the loop, I’ll copy the file and create registry entry if needed:

That’s all Folks.

That's all


Additional information

Add-Font function is a part of PPoShTools module which you can find on GitHub. I like verbosity of my code- it is way easier to debug what went wrong with your automation tools just by looking at the logs. I’m using Write-Log (also a function from PPoShTools module). By default it writes to screen, but with setting a proper context (Set-LogConfiguration ) I can have all my functions in current running workspace log to file or event log instead. No need to rewrite the code!

As always, if you have any comments - feel free to contact me.

P.S. How to allow non-admin user write/run code that requries administrative rights? Check out my previous post.