Created
June 3, 2025 06:04
-
-
Save glektarssza/9e581969e8073c4a030f7efc2d068636 to your computer and use it in GitHub Desktop.
Install Windows Fonts without Elevated Permissions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Install fonts. | |
.DESCRIPTION | |
Install the fonts to the system. | |
.PARAMETER Path | |
The paths to the font files to install. | |
.PARAMETER FontExtensions | |
A list of file extensions to consider as font files for installation. | |
.PARAMETER DryRun | |
Whether to perform a "dry run" and not actually perform any operations. | |
.PARAMETER Force | |
Whether to force an attempt at installation of the font even if it's already | |
installed. | |
.EXAMPLE | |
Install-Font -Path ./path/to/font.ttf | |
# Install a single font file. | |
.EXAMPLE | |
Install-Font -Path ./path/to/font.ttf -Force | |
# Install a single font file, overwriting any existing font already named | |
# "font.ttf". | |
.EXAMPLE | |
Install-Font -Path ./path/to/font.ttf,./path/to/another/font.otf | |
# Install multiple font files. | |
.EXAMPLE | |
Get-ChildItem -File -Path ./fonts/ | Install-Font | |
# Install all font files from a given folder. | |
#> | |
function Install-Fonts { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] | |
[ValidateNotNullOrWhiteSpace()] | |
[String] | |
$Path, | |
[Parameter(Mandatory = $false)] | |
[ValidateNotNullOrWhiteSpace()] | |
[String[]] | |
$FontExtensions = @(".ttf", ".otf", ".woff", ".woff2", ".eot"), | |
[Parameter(Mandatory = $false)] | |
[switch] | |
$DryRun, | |
[Parameter(Mandatory = $false)] | |
[switch] | |
$Force | |
) | |
Begin { | |
$stagingDirPath = "C:\Windows\Temp\Fonts"; | |
Write-Debug "Font staging directory is at '$stagingDirPath'"; | |
if (-not $DryRun) { | |
$stagingDir = New-Item -Path $stagingDirPath -ItemType Directory -Force; | |
} | |
$stagedFonts = [System.Collections.Generic.List[string]]::new(); | |
$installedFonts = [System.Collections.Generic.List[System.IO.FileInfo]]::new(); | |
Get-ChildItem -Path "$env:LOCALAPPDATA\Microsoft\Windows\Fonts" -Recurse -File | ForEach-Object { | |
$file = $_; | |
if (([System.Linq.Enumerable]::Any([string[]]$FontExtensions, [System.Func[string, bool]] { | |
param($ext) | |
return $file.Name.ToLower().EndsWith($ext.ToLower()); | |
}))) { | |
Write-Debug "Found installed font '$($file.Name)' at '$($file.FullName)'"; | |
$installedFonts.Add($file) | Out-Null; | |
} | |
} | |
Get-ChildItem -Path "C:\Windows\Fonts" -Recurse -File | ForEach-Object { | |
$file = $_; | |
if (([System.Linq.Enumerable]::Any([string[]]$FontExtensions, [System.Func[string, bool]] { | |
param($ext) | |
return $file.Name.ToLower().EndsWith($ext.ToLower()); | |
}))) { | |
Write-Debug "Found installed font '$($file.Name)' at '$($file.FullName)'"; | |
$installedFonts.Add($file) | Out-Null; | |
} | |
} | |
} | |
Process { | |
if (-not (Test-Path -Path $Path -PathType Leaf)) { | |
Write-Warning "Font file '$Path' is not a file!"; | |
return; | |
} | |
$file = Get-Item -Path $Path; | |
Write-Debug "Considering font '$($file.FullName)' for installation..."; | |
if (-not ([System.Linq.Enumerable]::Any([string[]]$FontExtensions, [System.Func[string, bool]] { | |
param($ext) | |
return $file.Name.ToLower().EndsWith($ext.ToLower()); | |
}))) { | |
Write-Debug "Font '$($file.Name)' failed the extension check, skipping!"; | |
return; | |
} | |
if ( ([System.Linq.Enumerable]::Any($installedFonts, [System.Func[System.IO.FileInfo, bool]] { | |
param($installedFont) | |
return ($file.BaseName.ToLower() -eq $installedFont.BaseName.ToLower()); | |
})) -and (-not $Force)) { | |
Write-Debug "Font '$($file.Name)' is already installed, skipping!"; | |
return; | |
} | |
if (-not $DryRun) { | |
Write-Debug "Staging font '$($file.Name)' for installation..."; | |
Copy-Item -Path $file -Destination $stagingDir; | |
} | |
else { | |
Write-Debug "Would have staged font '$($file.Name)' for installation..."; | |
} | |
$stagedFonts.Add((Join-Path -Path $stagingDirPath -ChildPath $file.Name)) | Out-Null; | |
} | |
End { | |
Write-Debug "Starting font install process..."; | |
Write-Debug "Getting font install directory..."; | |
# NOTE: Little Windows trick to get the special font folder object | |
$fontDestination = (New-Object -ComObject Shell.Application).Namespace(0x14); | |
$fontDestinationPath = $local:fontDestination.Self.Path; | |
Write-Debug "Font install directory is at '$fontDestinationPath'"; | |
if (-not $DryRun) { | |
Write-Debug "Installing fonts from staging directory at '$stagingDirPath'..."; | |
if ($stagedFonts.Count -le 0) { | |
Write-Warning "All fonts requested to for install are already installed!"; | |
} else { | |
$fontDestination.CopyHere((New-Object -ComObject Shell.Application).Namespace($stagingDirPath).Items(), 0x10); | |
Write-Debug "Cleaning up staging directory..."; | |
$stagedFonts | ForEach-Object { | |
Remove-Item -Path $_ -Force -ErrorAction SilentlyContinue; | |
} | |
} | |
} | |
else { | |
$stagedFonts | ForEach-Object { | |
Write-Debug "Would have installed '$([System.IO.Path]::GetFileName($_))'"; | |
} | |
} | |
Write-Debug "Done font install process"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment