Add-Font - Remotely with PowerShell
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:
- copying the file using
Shell.Application
methods and proper copy flag options - creating the registry entry manually
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)?
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:
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:
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 S
ystem.Drawing` type object:
Still in the loop, I’ll copy the file and create registry entry if needed:
That’s all Folks.
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.
Leave a comment