Utf8BomHeader.psm1

$cs = @"
using System;
using System.IO;
using System.Linq;

public class Utf8BomHeaderCompareResult
{
    public string SourceFile { get; private set; }
    public string SourceHeader { get; private set; }
    public string Equality { get; private set; }
    public string CompareFile { get; private set; }
    public string CompareHeader { get; private set; }
    
    public Utf8BomHeaderCompareResult(string sourceFile, string sourceHeader, string equality, string compareFile, string compareHeader)
    {
        SourceFile = sourceFile;
        SourceHeader = sourceHeader;
        Equality = equality;
        CompareFile = compareFile;
        CompareHeader = compareHeader;
    }
}

public static class FileHeader
{
    public static string Read(string path, int count)
    {
        using (var stream = new System.IO.FileStream(path, FileMode.Open))
        {
            var reads = Enumerable.Range(1, count).Select(x => stream.ReadByte().ToString("X2")).ToArray();
            return string.Join("", reads);
        }
    }

    public static string Read(FileInfo file, int count)
    {
        using (var stream = file.OpenRead())
        {
            var reads = Enumerable.Range(1, count).Select(x => stream.ReadByte().ToString("X2")).ToArray();
            return string.Join("", reads);
        }
    }

    public static void Write(string source, string dest, byte[] header)
    {
        var buffer = File.ReadAllBytes(source);
        var combine = Combine(header, buffer);
        File.WriteAllBytes(dest, combine);
    }

    public static void Write(FileInfo file, string dest, byte[] header)
    {
        byte[] buffer = null;
        using (var stream = file.OpenRead())
        {
            buffer = new byte[stream.Length];
            stream.Read(buffer, 0, (int)stream.Length);
        }
        var combine = Combine(header, buffer);
        File.WriteAllBytes(dest, combine);
    }

    public static void Remove(string source, string dest, int offset)
    {
        byte[] buffer = null;
        using (var stream = new FileStream(source, FileMode.Open))
        {
            buffer = new byte[stream.Length - offset];
            stream.Seek(offset, SeekOrigin.Current);
            stream.Read(buffer, 0, (int)stream.Length - offset);
        }
        File.WriteAllBytes(dest, buffer);
    }

    public static void Remove(FileInfo source, string dest, int offset)
    {
        byte[] buffer = null;
        using (var stream = source.OpenRead())
        {
            buffer = new byte[stream.Length - offset];
            stream.Seek(offset, SeekOrigin.Current);
            stream.Read(buffer, 0, (int)stream.Length - offset);
        }
        File.WriteAllBytes(dest, buffer);
    }
    
    public static Utf8BomHeaderCompareResult Compare(string sourcePath, string comparePath, int count, string check)
    {
        var sourceHeader = Read(sourcePath, count);
        var compareHeader = Read(comparePath, count);
        var sourceSymbol = sourceHeader == check ? "=" : "<";
        var compareSymbol = compareHeader == check ? "=" : ">";
        return new Utf8BomHeaderCompareResult(sourcePath, sourceHeader, sourceSymbol + compareSymbol, comparePath, compareHeader);
    }

    private static byte[] Combine(byte[] first, byte[] second)
    {
        byte[] ret = new byte[first.Length + second.Length];
        Buffer.BlockCopy(first, 0, ret, 0, first.Length);
        Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
        return ret;
    }
}
"@

Add-Type -TypeDefinition $cs -Language CSharp

New-Variable -Name removeOffset -Value 3 -Option Constant
New-Variable -Name bomHex -Value "EFBBBF" -Option Constant
New-Variable -Name bom -Value 239,187,191 -Option Constant

function Add-Utf8BomHeader {
    [CmdletBinding(DefaultParameterSetName = "File")]
    [OutputType([void])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "File")]
        [System.IO.FileInfo]$File,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [Alias("LiteralPath", "PSPath")]
        [string]$Path,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$OutputPath,

        [Switch]$Force
    )

    process {
        if ($PSBoundParameters.ContainsKey("File")) {
            if (!$Force) {
                $header = [FileHeader]::Read($File, $removeOffset)
                if ($header -eq $bomHex) {
                    Write-Verbose "Bom already exists, detected header : $header"
                    return
                }
            } else {
                Write-Verbose "-Force paramter detected, skip checking bom header before operation."
            }
            [FileHeader]::Write($File, $OutputPath, $bom)
        }
        else {
            if (!$Force) {
                $header = [FileHeader]::Read($Path, $removeOffset)
                if ($header -eq $bomHex) {
                    Write-Verbose "Bom already exists, detected header : $header"
                    return
                }
            } else {
                Write-Verbose "-Force paramter detected, skip checking bom header before operation."
            }
            [FileHeader]::Write($Path, $OutputPath, $bom)
        }
    }
}

function Remove-Utf8BomHeader {
    [CmdletBinding(DefaultParameterSetName = "File")]
    [OutputType([void])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "File")]
        [System.IO.FileInfo]$File,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [Alias("LiteralPath", "PSPath")]
        [string]$Path,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$OutputPath,

        [Switch]$Force
    )

    process {
        if ($PSBoundParameters.ContainsKey("File")) {
            if (!$Force) {
                $header = [FileHeader]::Read($File, $removeOffset)
                if ($header -ne $bomHex) {
                    Write-Verbose "Bom already missing, detected header : $header"
                    return
                }
            } else {
                Write-Verbose "-Force paramter detected, skip checking bom header before operation."
            }
            [FileHeader]::Remove($File, $OutputPath, $removeOffset)
        }
        else {
            if (!$Force) {
                $header = [FileHeader]::Read($Path, $removeOffset)
                if ($header -ne $bomHex) {
                    Write-Verbose "Bom already missing, detected header : $header"
                    return
                }
            } else {
                Write-Verbose "-Force paramter detected, skip checking bom header before operation."
            }
            [FileHeader]::Remove($Path, $OutputPath, $removeOffset)
        }
    }
}

function Get-Utf8BomHeader {
    [CmdletBinding(DefaultParameterSetName = "File")]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "File")]
        [System.IO.FileInfo]$File,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [Alias("LiteralPath", "PSPath")]
        [string]$Path,

        [Alias("Count")]
        [int]$ReadCount = $removeOffset
    )

    process {
        if ($PSBoundParameters.ContainsKey("File")) {
            $header = [FileHeader]::Read($File, $ReadCount)
            Write-Output $header
        }
        else {
            $header = [FileHeader]::Read($Path, $ReadCount)
            Write-Output $header
        }
    }
}

function Compare-Utf8BomHeader {
    [OutputType([Utf8BomHeaderCompareResult])]
    param(
        [Parameter(Mandatory = $true)]
        [Alias("Source")]
        [string]$ReferenceFile,

        [Parameter(Mandatory = $true)]
        [Alias("Target")]
        [string]$DifferenceFile
    )

    $compare = [FileHeader]::Compare($ReferenceFile, $DifferenceFile, $removeOffset, $bomHex)
    Write-Output $compare
}

function Test-Utf8BomHeader {
    [CmdletBinding(DefaultParameterSetName = "File")]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "File")]
        [System.IO.FileInfo]$File,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [Alias("LiteralPath", "PSPath")]
        [string]$Path
    )

    process {
        if ($PSBoundParameters.ContainsKey("File")) {
            $header = [FileHeader]::Read($File, $removeOffset)
            Write-Output ($header -eq $bomHex)
        }
        else {
            $header = [FileHeader]::Read($Path, $removeOffset)
            Write-Output ($header -eq $bomHex)
        }
    }
}

function Test-Utf8BomHeaderPS {
    [CmdletBinding(DefaultParameterSetName = "File")]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "File")]
        [System.IO.FileInfo]$File,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "Path")]
        [Alias("LiteralPath", "PSPath")]
        [string]$Path
    )

    process {
        if ($PSBoundParameters.ContainsKey("File")) {
            [System.IO.FileStream]$stream
            try {
                $stream = $File.OpenRead()
                $header = (1..3 | ForEach-Object {$stream.ReadByte().ToString("X2")}) -join ""
                Write-Output ($header -eq $bomHex)
            }
            finally {
                $stream.Dispose();
            }
        }
        else {
            [System.IO.FileStream]$stream
            try {
                $stream = [System.IO.FileStream]::New($Path, [System.IO.FileMode]::Open)
                $header = (1..3 | ForEach-Object {$stream.ReadByte().ToString("X2")}) -join ""
                Write-Output ($header -eq $bomHex)
            }
            finally {
                $stream.Dispose();
            }
        }
    }
}

# $path = "D:\GitHub\guitarrapc\AWSLambdaCSharpIntroduction\AWSLambdaCSharpIntroduction.sln"
# $remove = "D:\GitHub\remove.sln"
# $add = "D:\GitHub\add.sln"
# Import-Module ./Utf8BomHeader.psm1 -Force -Verbose
# ls $path -File | Where-Object {Test-Utf8BomHeader -File $_}
# ls $path -File | Where-Object {Test-Utf8BomHeader -File $_} | Get-Utf8BomHeader
# ls $path -File | Where-Object {Test-Utf8BomHeader -File $_} | Remove-Utf8BomHeader -OutputPath $remove
# Get-Utf8BomHeader -Path $remove
# Add-Utf8BomHeader -Path $remove -OutputPath $add
# Get-Utf8BomHeader -Path $add
# Compare-Utf8BomHeader -ReferenceFile $path -DifferenceFile $add
Export-ModuleMember -Function *-Utf8BomHeader, *-Utf8BomHeaderPS