Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heetbeet/14a15e1dd212bca6a517ddd98a9ddc8e to your computer and use it in GitHub Desktop.
Save heetbeet/14a15e1dd212bca6a517ddd98a9ddc8e to your computer and use it in GitHub Desktop.
Temporary Font Install?

Did you know you can install fonts without elevation?

The catch is that they're only available for the duration of your session. They are, however, available in all apps across the system.

Someone asked about how to do it on Facebook this week, and at first, I just pointed them at the install script for PowerLineFonts which loops through all the fonts in a folder and install them.

I've used this more than a few times to install some fonts, including the PowerLine ones, which are great:

$sa = New-Object -ComObject Shell.Application
$fonts =  $sa.NameSpace("shell:fonts")

foreach($font in Get-ChildItem -Recurse -Include *.ttf, *.otg) { 
    $fonts.CopyHere($font.FullName)
}

The problem is that this seems to require you to have administrative rights (it's actually putting the fonts in C:\Windows\Fonts), and this guy didn't have that, and was looking for a way to install the fonts just for a single session. It turns out there is a Win32 API for that.

Now, when you see a windows API that simple (it just takes a string), you can just write the DllImport yourself. However, if you don't know where to start, there's something even simpler. Look it up on PInvoke and you can just copy paste the C# declaration. You just have to call Add-Type and specify the type name, and an empty namespace, and make sure that the declaration has public on the front. This one looks like this:

add-type -name Session -namespace "" -member @"
[DllImport("gdi32.dll")]
public static extern int AddFontResource(string filePath);
"@

$null = foreach($font in Get-ChildItem -Recurse -Include *.ttf, *.otg) {
    [Session]::AddFontResource($font.FullName)
}

That's all there is to it. It adds the font to your session, and it'll be gone after reboot.

Technically, you're supposed to call RemoveFontResource to unload the fonts, and notify everyone when you've added (or removed) fonts by sending a WM_FONTCHANGE broadcast message. That gets a little more complicated, but the whole thing is actually there as the C# code for an executable on that PInvoke page.

We could map that to PowerShell wrapping Get-ChildItem if we wanted to, and even stick it into a Fonts module ;-)

add-type -name Session -namespace "" -member @"
[DllImport("gdi32.dll")]
public static extern bool AddFontResource(string filePath);
[DllImport("gdi32.dll")]
public static extern bool RemoveFontResource(string filePath);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool PostMessage(IntPtr hWnd, int Msg, int wParam = 0, int lParam = 0);
"@
function Get-Font {
[CmdletBinding(DefaultParameterSetName='Items', SupportsTransactions=$true, HelpUri='http://go.microsoft.com/fwlink/?LinkID=113308')]
param(
[Parameter(ParameterSetName='Items', Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralItems', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[Parameter(Position=1)]
[string]
${Filter},
[string[]]
${Include},
[string[]]
${Exclude},
[Alias('s')]
[switch]
${Recurse},
[uint32]
${Depth},
[switch]
${Force},
[switch]
${Name}
)
dynamicparam
{
try {
$targetCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet, $PSBoundParameters)
$dynamicParams = @($targetCmd.Parameters.GetEnumerator() | Microsoft.PowerShell.Core\Where-Object { $_.Value.IsDynamic })
if ($dynamicParams.Length -gt 0)
{
$paramDictionary = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
foreach ($param in $dynamicParams)
{
$param = $param.Value
if(-not $MyInvocation.MyCommand.Parameters.ContainsKey($param.Name))
{
$dynParam = [Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
$paramDictionary.Add($param.Name, $dynParam)
}
}
return $paramDictionary
}
} catch {
throw
}
}
begin
{
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$PSBoundParameters['Include'] = "*.fon", "*.fnt", "*.ttf", "*.ttc", "*.fot", "*.otf", "*.mmm", "*.pfb", "*.pfm"
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\Get-ChildItem', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
if($MyInvocation.InvocationName -eq "Add-Font") {
$scriptCmd = {& $wrappedCmd @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object {
if([Session]::AddFontResource($_.FullName)) {
Write-Verbose "Added Font: $(Resolve-Path $_.FullName -Relative)"
}
else {
Write-Warning "Failed on $(Resolve-Path $_.FullName -Relative)"
}
} }
} elseif($MyInvocation.InvocationName -eq "Remove-Font") {
$scriptCmd = {& $wrappedCmd @PSBoundParameters | Microsoft.PowerShell.Core\ForEach-Object {
if([Session]::RemoveFontResource($_.FullName)) {
Write-Verbose "Removed Font: $(Resolve-Path $_.FullName -Relative)"
}
else {
Write-Warning "Failed on $(Resolve-Path $_.FullName -Relative)"
}
} }
}
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
$EVERYONE = New-Object IntPtr 0xffff
$FONTCHANGE = 0x1D
$NULL = [Session]::PostMessage($EVERYONE, $FONTCHANGE)
}
<#
.ForwardHelpTargetName Microsoft.PowerShell.Management\Get-ChildItem
.ForwardHelpCategory Cmdlet
#>
}
Set-Alias Add-Font Get-Font
Set-Alias Remove-Font Get-Font
Export-ModuleMember -Function "Get-Font" -Alias "Add-Font", "Remove-Font"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment