2

Below is my script istalling Monserrat fonts from zip file. I can't figure how to check if a font already installed. After installation I can open folder C:\Windows\Fonts\Montserrat and I see al of them. When I am running script second time, it is not recognize existance of this folder. Where is my mistake?

$Source = "Montserrat.zip"
$FontsFolder = "FontMontserrat"
Expand-Archive $Source -DestinationPath $FontsFolder
$FONTS = 0x14
$CopyOptions = 4 + 16;
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
$allFonts = dir $FontsFolder
foreach($File in $allFonts)
{
    If((Test-Path "C:\Windows\Fonts\Montserrat") -eq $True)
    {
        echo "Font $File already installed"
    }
    Else
    {
        echo "Installing $File"
        $CopyFlag = [String]::Format("{0:x}", $CopyOptions);
        $objFolder.CopyHere($File.fullname,$CopyFlag)
    }
}
Squashman
  • 13,649
  • 5
  • 27
  • 36
Arkady Karasin
  • 159
  • 6
  • 17
  • 1
    If you're on Windows 10, fonts are not installed into a folder, so your check is incorrect. Also, the location is privileged so you need to ensure powershell is running as admin. – Maximilian Burszley Apr 01 '20 at 13:50
  • Modify your code so `$allFonts = dir c:\windows\fonts | select -expand Name` then instead of `Test-Path`, you can use `if($allFonts -contains $file.Name){...` to check if the font is installed or not. – FoxDeploy Apr 01 '20 at 13:58

3 Answers3

2

Finally my script:

$Source = "Montserrat.zip"
$FontsFolder = "FontMontserrat"
Expand-Archive $Source -DestinationPath $FontsFolder -Force
$FONTS = 0x14
$CopyOptions = 4 + 16;
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
$allFonts = dir $FontsFolder
foreach($font in Get-ChildItem -Path $fontsFolder -File)
{
    $dest = "C:\Windows\Fonts\$font"
    If(Test-Path -Path $dest)
    {
        echo "Font $font already installed"
    }
    Else
    {
        echo "Installing $font"
        $CopyFlag = [String]::Format("{0:x}", $CopyOptions);
        $objFolder.CopyHere($font.fullname,$CopyFlag)
    }
}

I am running this script by following cmd:

set batchPath=%~dp0
powershell.exe -noexit -file "%batchPath%InstMontserrat.ps1"

I don't have to run it as administrator, but user have admin permissions.

Arkady Karasin
  • 159
  • 6
  • 17
  • This works. Note that on Windows 10 20H2, even in an elevated prompt, the above code installs my font into my local application data folder: `~\AppData\Local\Microsoft\Windows\Fonts`. – Aaron Jensen Jun 03 '21 at 19:44
0

Corrections of your script based on my comment assuming Windows 10:

# well-known SID for admin group
if ('S-1-5-32-544' -notin [System.Security.Principal.WindowsIdentity]::GetCurrent().Groups) {
    throw 'Script must run as admin!'
}

$source = 'Montserrat.zip'
$fontsFolder = 'FontMontserrat'

Expand-Archive -Path $source -DestinationPath $fontsFolder

foreach ($font in Get-ChildItem -Path $fontsFolder -File) {
    $dest = "C:\Windows\Fonts\$font"
    if (Test-Path -Path $dest) {
        "Font $font already installed."
    }
    else {
        $font | Copy-Item -Destination $dest
    }
}
Maximilian Burszley
  • 18,243
  • 4
  • 34
  • 63
0

If you do not want to install the font on OS level but only make it available for programs to use until reboot you may want to use this script that:

  • Will fail/throw if it cannot register/unregister font.
  • Broadcasts WM_FONTCHANGE to inform all windows that fonts have changed
  • Does not require administrator privileges
  • Does not install fonts in Windows, only makes them available for all programs in current session until reboot
  • Has verbose mode for debugging
  • Does not work with font folders

Usage:

register-fonts.ps1 [-v] [-unregister <PATH>[,<PATH>...]] [-register  <PATH>[,<PATH>...]] # Register and unregister at same time
register-fonts.ps1 [-v] -unregister <PATH>
register-fonts.ps1 [-v] -register <PATH>
register-fonts.ps1 [-v] <PATH> # Will register font path
Param (
  [Parameter(Mandatory=$False)]
  [String[]]$register,

  [Parameter(Mandatory=$False)]
  [String[]]$unregister
)

# Stop script if command fails https://stackoverflow.com/questions/9948517/how-to-stop-a-powershell-script-on-the-first-error
$ErrorActionPreference = "Stop"

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);
"@

$broadcast = $False;
Foreach ($unregisterFontPath in $unregister) {
  Write-Verbose "Unregistering font $unregisterFontPath"
  # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-removefontresourcea
  $success = [Session]::RemoveFontResource($unregisterFontPath)
  if (!$success) {
    Throw "Cannot unregister font $unregisterFontPath"
  }
  $broadcast = $True
}

Foreach ($registerFontPath in $register) {
  Write-Verbose "Registering font $registerFontPath"
  # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcea
  $success = [Session]::AddFontResource($registerFontPath)
  if (!$success) {
    Throw "Cannot register font $registerFontPath"
  }
  $broadcast = $True
}

if ($broadcast) {
  # HWND_BROADCAST https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea
  $HWND_BROADCAST = New-Object IntPtr 0xffff
  # WM_FONTCHANGE https://learn.microsoft.com/en-us/windows/win32/gdi/wm-fontchange
  $WM_FONTCHANGE  = 0x1D

  Write-Verbose "Broadcasting font change"
  # Broadcast will let other programs know that fonts were changed https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-postmessagea
  $success = [Session]::PostMessage($HWND_BROADCAST, $WM_FONTCHANGE)
  if (!$success) {
    Throw "Cannot broadcase font change"
  }
}

The script was inspired by this gist https://gist.github.com/Jaykul/d53a16ce5e7d50b13530acb4f98aaabd

Aalex Gabi
  • 1,525
  • 1
  • 18
  • 32