DSCResources/cFont/cFont.psm1

$CSharpCode = @'
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
 
namespace FontResource
{
    public class AddRemoveFonts
    {
        private static IntPtr HWND_BROADCAST = new IntPtr(0xffff);
 
        [DllImport("gdi32.dll")]
        static extern int AddFontResource(string lpFilename);
 
        [DllImport("gdi32.dll")]
        static extern int RemoveFontResource(string lpFileName);
 
        [DllImport("user32.dll",CharSet=CharSet.Auto)]
        private static extern int SendMessage(IntPtr hWnd, WM wMsg, IntPtr wParam, IntPtr lParam);
 
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool PostMessage(IntPtr hWnd, WM Msg, IntPtr wParam, IntPtr lParam);
 
        public static bool PostFontChangedMessage() {
            bool posted = PostMessage(HWND_BROADCAST, WM.FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
            return posted;
        }
 
        public static int AddFont(string fontFilePath) {
            FileInfo fontFile = new FileInfo(fontFilePath);
            if (!fontFile.Exists){
                return 0;
            }
            try{
                int retVal = AddFontResource(fontFilePath);
                bool posted = PostMessage(HWND_BROADCAST, WM.FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
                return retVal;
            }
            catch{
                return 0;
            }
        }
 
        public static int RemoveFont(string fontFileName) {
            try{
                int retVal = RemoveFontResource(fontFileName);
                bool posted = PostMessage(HWND_BROADCAST, WM.FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
                return retVal;
            }
            catch {
                return 0;
            }
        }
 
        public enum WM : uint
        {
            FONTCHANGE = 0x001D
        }
 
    }
}
'@

Add-Type $CSharpCode

# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [ValidateSet("Present","Absent")]
        [string]
        $Ensure = 'Present',

        [parameter(Mandatory = $true)]
        [ValidatePattern('\.(ttf|ttc|otf|fon)$')]
        [string]
        $FontFile,

        [pscredential]
        $Credential
    )
    $GetRes = @{
        Ensure = $Ensure
        FontFile = $FontFile
        Credential = $Credential
    }
    $FontFolder = Join-Path $Env:windir '\Fonts'
    $Filename = Split-Path $FontFile -Leaf

    if(Test-Path (Join-Path $FontFolder $Filename) -PathType Leaf){
        $GetRes.Ensure = 'Present'
    }
    else{
        $GetRes.Ensure = 'Absent'
    }

    $GetRes
} # end of Get-TargetResource



# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [ValidateSet("Present","Absent")]
        [string]
        $Ensure = 'Present',

        [parameter(Mandatory = $true)]
        [ValidatePattern('\.(ttf|ttc|otf|fon)$')]
        [string]
        $FontFile,

        [pscredential]
        $Credential
    )

    return ((Get-TargetResource @PSBoundParameters).Ensure -eq $Ensure)
} # end of Test-TargetResource

# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [ValidateSet("Present","Absent")]
        [string]
        $Ensure = 'Present',

        [parameter(Mandatory = $true)]
        [ValidatePattern('\.(ttf|ttc|otf|fon)$')]
        [string]
        $FontFile,

        [pscredential]
        $Credential
    )
    $ErrorActionPreference = 'Stop'

    if($Ensure -eq 'Absent'){
    #アンインストール
        $Filename = Split-Path $FontFile -Leaf
        Write-Verbose ('Uninstalling font...')
        UnInstall-Font $Filename -ErrorAction Stop
    }
    elseif($Ensure -eq 'Present'){
    #インストール
        $private:tmpFolder = $Env:TEMP
        $TempFont = (Get-RemoteFile -Path $FontFile -DestinationFolder $tmpFolder -Credential $Credential -Force -PassThru -ErrorAction Stop)   # TEMPフォルダに一度コピー
        if(Test-Path $TempFont){
            try{
                Write-Verbose ('Installing font...')
                Install-Font $TempFont.Fullname -ErrorAction Stop
                Write-Verbose ('Font Installed')
            }
            catch{
                Write-Error $_.Exception.Message
            }
            finally{
                if(Test-Path $TempFont){
                    Remove-Item $TempFont -Force -ErrorAction SilentlyContinue
                }
            }
        }
        else{
            Write-Error 'Failed to copy the font file'
        }
    }

} # end of Set-TargetResource

# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
function Install-Font {
    # C:\Windows\Fontsにファイルコピーしただけではインストールされない
    # shell.application経由でコピーする
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory,Position=0)]
        [ValidateScript({Test-Path $_})]
        $Path
    )
    $FullPath = Resolve-Path $Path -ErrorAction Stop
    $local:FONTS = 0x14;
    $local:CopyOptions = 4 + 16 + 1024  #GUIを非表示にするオプション
    $ObjShell = New-Object -ComObject Shell.Application
    $FontsFolder = $ObjShell.Namespace($FONTS)
    try{
        $FontsFolder.CopyHere($FullPath.Path, $CopyOptions)
        [void][FontResource.AddRemoveFonts]::PostFontChangedMessage()
    }
    catch{
        Write-Error $_.Exception.Message
    }
}

# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
function UnInstall-Font {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory,Position=0)]
        $Font
    )
    $RegFont = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"
    $FontFolder = Join-Path $Env:windir '\Fonts'
    $Filename = Split-Path $Font -Leaf
    $FontPath = Join-Path $FontFolder $Filename

    if(-not (Test-Path $FontPath -PathType Leaf)){
        Write-Error 'Font file not found'
        return
    }

    if(-not [FontResource.AddRemoveFonts]::RemoveFont($FontPath)){
        Write-Error 'Remove font failed'
    }
    else{
        #Write-Verbose 'Remove font succeeded'
        Start-Sleep -Seconds 2  # ちょっと待たないと失敗することがあるような、ないような...
        if($Value = (Get-ItemProperty $RegFont).PsObject.Properties | where {$_.value -eq $Filename}){
            Remove-ItemProperty -Path $RegFont -Name $Value.Name
        }
        if(Test-Path $FontPath){
            Remove-Item $FontPath -Force -ErrorAction Stop
        }
        Write-Verbose 'Remove font succeeded'
    }
}

# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
function Get-RemoteFile {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true, Position=0)]
        [Alias("Uri")]
        [Alias("SourcePath")]
        [System.Uri[]] $Path, # ダウンロードするファイルパス(URI)

        [Parameter(Mandatory=$true, Position=1)]
        [string]$DestinationFolder, # ダウンロード先フォルダ

        [Parameter()]
        [AllowNull()]
        [pscredential]$Credential,  # 資格情報

        [Parameter()]
        [int]$TimeoutSec = 0,

        [Parameter()]
        [switch]$Force,

        [Parameter()]
        [switch]$PassThru
    )
    begin{
        if(-not (Test-Path $DestinationFolder -PathType Container)){
            Write-Verbose ('DestinationFolder Folder "{0}" is not exist. Will create it.' -f $DestinationFolder)
            New-Item $DestinationFolder -ItemType Directory -Force -ErrorAction Stop
        }
    }

    Process{
        foreach($private:tempPath in $Path){
            try{
                $private:OutFile = ''
                $private:valid = $true
                $private:tmpDriveName = [Guid]::NewGuid()

                if($tempPath.IsLoopback -eq $null){
                    $valid = $false
                    throw ("{0} is not valid uri." -f $tempPath)
                }

                # ファイルの場所によって処理分岐(ローカル or 共有フォルダ or Web)
                if($tempPath.IsLoopback -or $tempPath.IsUnc){ # ローカル or 共有フォルダ
                    # 資格情報を使う場合は一度ドライブをマップする必要あり
                    if($PSBoundParameters.Credential){
                        New-PSDrive -Name $tmpDriveName -PSProvider FileSystem -Root (Split-Path $tempPath.LocalPath) -Credential $Credential -ErrorAction Stop | Out-Null
                    }
                    # ローカルにコピーする
                    $OutFile = Join-Path $DestinationFolder ([System.IO.Path]::GetFileName($tempPath.LocalPath))
                    if(Test-Path $OutFile -PathType Leaf){
                        if($tempPath.LocalPath -eq $OutFile){
                            if($PassThru){
                                if(Test-Path $OutFile){
                                    Get-Item $OutFile
                                }
                            }
                            continue
                        }
                        elseif($Force){
                            Write-Warning ('"{0}" will be overwritten.'-f $OutFile)
                        }
                        else{
                            $valid = $false
                            throw ("'{0}' is exist. If you want to replace existing file, Use 'Force' switch." -f $OutFile)
                        }
                    }

                    Write-Verbose ("Copy file from '{0}' to '{1}'" -f $tempPath.LocalPath, $DestinationFolder)
                    Copy-Item -Path $tempPath.LocalPath -Destination $DestinationFolder -ErrorAction Stop -Force:$Force
                }
                elseif($tempPath.Scheme -match 'http|https|ftp'){
                    # WebからDL
                    $OutFile = Join-Path $DestinationFolder ([System.IO.Path]::GetFileName($tempPath.AbsoluteUri))
                    if(Test-Path $OutFile -PathType Leaf){
                        if($Force){
                            Write-Warning ('"{0}" will be overwritten.'-f $OutFile)
                        }
                        else{
                            $valid = $false
                            throw ("'{0}' is exist. If you want to replace existing file, Use 'Force' switch." -f $OutFile)
                        }
                    }

                    Write-Verbose ("Download file from '{0}' to '{1}'" -f $tempPath.AbsoluteUri, $OutFile)
                    $private:origVerbose = $VerbosePreference; $VerbosePreference = 'SilentlyContinue'
                    Invoke-WebRequest -Uri $tempPath.AbsoluteUri -OutFile $OutFile -Credential $Credential -TimeoutSec $TimeoutSec -ErrorAction stop
                    $VerbosePreference = $origVerbose
                }
                else{
                    $valid = $false
                    throw ("{0} is not valid uri." -f $tempPath)
                }

                if($valid -and $OutFile -and $PassThru){
                    if(Test-Path $OutFile){
                        Get-Item $OutFile
                    }
                }
            }
            catch [Exception]{
                Write-Error $_.Exception.Message
            }
            finally{
                if(Get-PSDrive | where {$_.Name -eq $tmpDriveName}){
                    Remove-PSDrive -Name $tmpDriveName -Force
                }
            }
        }
    }
}

# ////////////////////////////////////////////////////////////////////////////////////////
# ////////////////////////////////////////////////////////////////////////////////////////
Export-ModuleMember -Function *-TargetResource