diff --git a/Tools.ps1 b/Tools.ps1 index 01be0c2..f68e167 100644 --- a/Tools.ps1 +++ b/Tools.ps1 @@ -1126,6 +1126,185 @@ Function Create-NetworkDrive { catch { Write-Verbose "Could not set volume label (possibly unsupported context)." } } +function Install-SystemFont { + [CmdletBinding(SupportsShouldProcess=$true)] + param ( + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string]$FontPath + ) + + try { + $fontFile = Get-Item -LiteralPath $FontPath -ErrorAction Stop + } + catch { + Write-Error "Font file '$FontPath' could not be found or accessed: $($_.Exception.Message)" + return + } + + if ($fontFile.PSIsContainer) { + Write-Error "Font path '$FontPath' points to a directory. Specify a .otf or .ttf file." + return + } + + $extension = $fontFile.Extension.ToLowerInvariant() + if ($extension -notin @('.otf', '.ttf')) { + Write-Error "Only .otf and .ttf font files are supported." + return + } + + # Read the font's embedded family and face names instead of relying on + # language- and Windows-version-dependent Shell property column numbers. + try { + Add-Type -AssemblyName PresentationCore -ErrorAction Stop + $glyphTypeface = New-Object System.Windows.Media.GlyphTypeface ([Uri]$fontFile.FullName) + $englishLanguage = [System.Globalization.CultureInfo]::GetCultureInfo('en-US') + $fontFamily = $null + $fontFace = $null + + if (-not $glyphTypeface.FamilyNames.TryGetValue($englishLanguage, [ref]$fontFamily)) { + $fontFamily = $glyphTypeface.FamilyNames.Values | Select-Object -First 1 + } + if (-not $glyphTypeface.FaceNames.TryGetValue($englishLanguage, [ref]$fontFace)) { + $fontFace = $glyphTypeface.FaceNames.Values | Select-Object -First 1 + } + + if ([string]::IsNullOrWhiteSpace($fontFamily)) { + throw "The font does not contain a readable family name." + } + + if ([string]::IsNullOrWhiteSpace($fontFace) -or $fontFace -in @('Normal', 'Regular')) { + $fontName = $fontFamily + } + else { + $fontName = "$fontFamily $fontFace" + } + } + catch { + Write-Error "Font metadata could not be read from '$($fontFile.FullName)': $($_.Exception.Message)" + return + } + + $fileName = $fontFile.Name + $fontsDirectory = Join-Path $env:SystemRoot 'Fonts' + $destinationPath = Join-Path $fontsDirectory $fileName + $registryValueName = if ($extension -eq '.otf') { + "$fontName (OpenType)" + } + else { + "$fontName (TrueType)" + } + + try { + if (-not ('Emberkom.NativeFontMethods' -as [type])) { + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; + +namespace Emberkom +{ + public static class NativeFontMethods + { + [DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern int AddFontResource(string fileName); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr SendMessageTimeout( + IntPtr windowHandle, + uint message, + UIntPtr wParam, + IntPtr lParam, + uint flags, + uint timeout, + out UIntPtr result + ); + } +} +'@ -ErrorAction Stop + } + } + catch { + Write-Error "Windows font installation support could not be initialized: $($_.Exception.Message)" + return + } + + if (-not $PSCmdlet.ShouldProcess($destinationPath, "Install system font '$fontName'")) { + return + } + + try { + $copyRequired = $true + if (Test-Path -LiteralPath $destinationPath) { + $sourceHash = (Get-FileHash -LiteralPath $fontFile.FullName -Algorithm SHA256 -ErrorAction Stop).Hash + $destinationHash = (Get-FileHash -LiteralPath $destinationPath -Algorithm SHA256 -ErrorAction Stop).Hash + $copyRequired = $sourceHash -ne $destinationHash + + if (-not $copyRequired) { + Write-Output "Font file '$fileName' is already present in '$fontsDirectory'." + } + } + + if ($copyRequired) { + Copy-Item -LiteralPath $fontFile.FullName -Destination $destinationPath -Force -ErrorAction Stop + Write-Output "Copied font file '$fileName' to '$fontsDirectory'." + } + + $fontRegistryKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( + 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts', + $true + ) + if ($null -eq $fontRegistryKey) { + throw "The system Fonts registry key could not be opened for writing." + } + + try { + $fontRegistryKey.SetValue( + $registryValueName, + $fileName, + [Microsoft.Win32.RegistryValueKind]::String + ) + } + finally { + $fontRegistryKey.Dispose() + } + Write-Output "Registered '$registryValueName' as a system font." + + $fontsAdded = [Emberkom.NativeFontMethods]::AddFontResource($destinationPath) + if ($fontsAdded -eq 0) { + throw "Windows did not load '$destinationPath' into the current session." + } + Write-Output "Loaded '$fontName' into the current Windows session." + + # Notify applications in this session without allowing a hung window to + # block an unattended RMM script indefinitely. + $broadcastHandle = [IntPtr]0xffff + $fontChangeMessage = 0x001D + $abortIfHung = 0x0002 + $messageResult = [UIntPtr]::Zero + $notificationSent = [Emberkom.NativeFontMethods]::SendMessageTimeout( + $broadcastHandle, + $fontChangeMessage, + [UIntPtr]::Zero, + [IntPtr]::Zero, + $abortIfHung, + 1000, + [ref]$messageResult + ) + + if ($notificationSent -eq [IntPtr]::Zero) { + Write-Output "Installed system font '$fontName', but no application acknowledged the font-change notification." + return + } + + Write-Output "Installed system font '$fontName' successfully." + } + catch { + Write-Error "Failed to install font '$($fontFile.FullName)': $($_.Exception.Message)" + } +} +# Usage Example: +# Install-SystemFont -FontPath "C:\Path\To\YourFont.otf" + # Function to import the Syncro Module Function Import-RMMModule () { Try { Import-Module $env:SyncroModule -WarningAction SilentlyContinue -ErrorAction Stop } @@ -1244,4 +1423,4 @@ If ( $TimeAttributedToUser ) { # Check the RMM runtime variable to see if a ticket number was specified, if so then set the $TicketID If ( $TicketNumber ) { If ( $TicketNumber.Trim().Length -gt 1 ) { $Global:TicketNumber = $TicketNumber.Trim() } -} \ No newline at end of file +}