MemoryMappedFile.psm1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSAvoidAssignmentToAutomaticVariable', 'IsWindows',
    Justification = 'IsWindows doesnt exist in PS5.1'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSUseDeclaredVarsMoreThanAssignments', 'IsWindows',
    Justification = 'IsWindows doesnt exist in PS5.1'
)]
[CmdletBinding()]
param()
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$script:PSModuleInfo = Import-PowerShellDataFile -Path "$PSScriptRoot\$baseName.psd1"
$script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ }
$scriptName = $script:PSModuleInfo.Name
Write-Debug "[$scriptName] - Importing module"

if ($PSEdition -eq 'Desktop') {
    $IsWindows = $true
}

#region [functions] - [private]
Write-Debug "[$scriptName] - [functions] - [private] - Processing folder"
#region [functions] - [private] - [Get-InternalPSModule]
Write-Debug "[$scriptName] - [functions] - [private] - [Get-InternalPSModule] - Importing"
function Get-InternalPSModule {
    <#
        .SYNOPSIS
        Performs tests on a module.

        .EXAMPLE
        Test-PSModule -Name 'World'

        "Hello, World!"
    #>

    [CmdletBinding()]
    param (
        # Name of the person to greet.
        [Parameter(Mandatory)]
        [string] $Name
    )
    Write-Output "Hello, $Name!"
}
Write-Debug "[$scriptName] - [functions] - [private] - [Get-InternalPSModule] - Done"
#endregion [functions] - [private] - [Get-InternalPSModule]
#region [functions] - [private] - [Set-InternalPSModule]
Write-Debug "[$scriptName] - [functions] - [private] - [Set-InternalPSModule] - Importing"
function Set-InternalPSModule {
    <#
        .SYNOPSIS
        Performs tests on a module.

        .EXAMPLE
        Test-PSModule -Name 'World'

        "Hello, World!"
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function',
        Justification = 'Reason for suppressing'
    )]
    [CmdletBinding()]
    param (
        # Name of the person to greet.
        [Parameter(Mandatory)]
        [string] $Name
    )
    Write-Output "Hello, $Name!"
}
Write-Debug "[$scriptName] - [functions] - [private] - [Set-InternalPSModule] - Done"
#endregion [functions] - [private] - [Set-InternalPSModule]
Write-Debug "[$scriptName] - [functions] - [private] - Done"
#endregion [functions] - [private]
#region [functions] - [public]
Write-Debug "[$scriptName] - [functions] - [public] - Processing folder"
#region [functions] - [public] - [Close-MemoryMappedFile]
Write-Debug "[$scriptName] - [functions] - [public] - [Close-MemoryMappedFile] - Importing"
function Close-MemoryMappedFile {
    <#
        .SYNOPSIS
        Closes and disposes an existing memory-mapped file by name.

        .DESCRIPTION
        This function attempts to open an existing memory-mapped file with the specified name.
        If the file exists, it disposes of the file and frees the associated memory resources.
        If the file does not exist or has already been closed, a warning is displayed instead.
        This operation is useful for cleaning up unmanaged resources created with memory-mapped files.

        .EXAMPLE
        Close-MemoryMappedFile -Name 'MySharedMemory'

        Output:
        ```powershell
        VERBOSE: Memory-mapped file 'MySharedMemory' closed successfully.
        ```

        Closes the memory-mapped file named 'MySharedMemory' and disposes of its resources.

        .LINK
        https://psmodule.io/MemoryMappedFile/Functions/Close-MemoryMappedFile
    #>


    [CmdletBinding()]
    param(
        # The name of the memory-mapped file to close and dispose.
        [Parameter(Mandatory)]
        [string] $Name
    )

    try {
        $item = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting($Name)
    } catch {
        return $null
    }

    if ($item) {
        $item.Dispose()
        Write-Verbose "Memory-mapped file '$Name' closed successfully."
    }
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [Close-MemoryMappedFile] - Done"
#endregion [functions] - [public] - [Close-MemoryMappedFile]
#region [functions] - [public] - [Get-MemoryMappedFile]
Write-Debug "[$scriptName] - [functions] - [public] - [Get-MemoryMappedFile] - Importing"
function Get-MemoryMappedFile {
    <#
        .SYNOPSIS
        Retrieves an existing memory-mapped file by name.

        .DESCRIPTION
        This function attempts to open an existing memory-mapped file identified by the provided name.
        If the file does not exist or an error occurs during access, the function returns $null instead
        of throwing an exception.

        .EXAMPLE
        Get-MemoryMappedFile -Name 'SharedMemoryBlock'

        Output:
        ```powershell
        SafeMemoryMappedFileHandle
        --------------------------
        Microsoft.Win32.SafeHandles.SafeMemoryMappedFileHandle
        ```

        Retrieves the memory-mapped file named 'SharedMemoryBlock'.

        .OUTPUTS
        System.IO.MemoryMappedFiles.MemoryMappedFile

        .NOTES
        The memory-mapped file object if successful.
        null. Returned when the specified memory-mapped file does not exist or an error occurs.

        .LINK
        https://psmodule.io/Memory/Functions/Get-MemoryMappedFile
    #>


    [OutputType([System.IO.MemoryMappedFiles.MemoryMappedFile])]
    [CmdletBinding()]
    param(
        # The name of the memory-mapped file to open.
        [Parameter(Mandatory)]
        [string] $Name
    )
    try {
        return [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting($Name)
    } catch {
        return $null
    }
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [Get-MemoryMappedFile] - Done"
#endregion [functions] - [public] - [Get-MemoryMappedFile]
#region [functions] - [public] - [New-MemoryMappedFile]
Write-Debug "[$scriptName] - [functions] - [public] - [New-MemoryMappedFile] - Importing"
function New-MemoryMappedFile {
    <#
        .SYNOPSIS
        Creates a memory-mapped file from a specified file path and name.

        .DESCRIPTION
        This function creates a memory-mapped file using a specified name and backing file path.
        If the file exists, it uses the existing file and its size. If the file does not exist, a new file
        is created at the specified path. The memory-mapped file is created with a default size of 1MB unless
        otherwise specified. The function returns a [System.IO.MemoryMappedFiles.MemoryMappedFile] object.

        .EXAMPLE
        New-MemoryMappedFile -Name 'SharedMap' -Path 'C:\Temp\shared.dat'

        Output:
        ```powershell
        Capacity : 1048576
        SafeMemoryMappedFileHandle : Microsoft.Win32.SafeHandles.SafeMemoryMappedFileHandle
        ```

        Creates a memory-mapped file named 'SharedMap' backed by 'C:\Temp\shared.dat' with a default size of 1MB.

        .OUTPUTS
        System.IO.MemoryMappedFiles.MemoryMappedFile

        .NOTES
        Represents the memory-mapped file created using the specified parameters.
        The returned object can be used for inter-process communication or efficient file access.

        .LINK
        https://psmodule.io/MemoryMapped/Functions/New-MemoryMappedFile/
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'The function only creates a memory-mapped file and does not modify system state in a way that requires confirmation.'
    )]
    [OutputType([System.IO.MemoryMappedFiles.MemoryMappedFile])]
    [CmdletBinding()]
    param(
        # The unique name for the memory-mapped file.
        [Parameter(Mandatory)]
        [string] $Name,

        # Specifies the path to the file backing the memory-mapped file.
        [Parameter(Mandatory)]
        [string] $Path,

        # Specifies the size of the memory-mapped file in bytes. Defaults to 1MB.
        [Parameter()]
        [long] $Size = 1Mb
    )

    if (Test-Path $Path) {
        $file = Get-Item -Path $Path
        $Size = $file.Length
        if ($Size -gt 0) {
            Write-Verbose "Opening existing file from '$Path' as '$Name'"
            return [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateFromFile(
                $Path,
                [System.IO.FileMode]::OpenOrCreate,
                $Name
            )
        }
    }

    Write-Verbose "Opening new file at '$Path' as '$Name'"
    return [System.IO.MemoryMappedFiles.MemoryMappedFile]::CreateFromFile(
        $Path,
        [System.IO.FileMode]::OpenOrCreate,
        $Name,
        $Size
    )
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [New-MemoryMappedFile] - Done"
#endregion [functions] - [public] - [New-MemoryMappedFile]
#region [functions] - [public] - [Read-MemoryMappedFileContent]
Write-Debug "[$scriptName] - [functions] - [public] - [Read-MemoryMappedFileContent] - Importing"
function Read-MemoryMappedFileContent {
    <#
        .SYNOPSIS
        Reads the content of an existing memory-mapped file.

        .DESCRIPTION
        This function opens a memory-mapped file by name and reads its entire content as a UTF-8 encoded string.
        If the file does not exist or cannot be accessed, the function returns null.
        Leading and trailing null characters are trimmed from the result.

        .EXAMPLE
        Read-MemoryMappedFileContent -Name 'SharedBuffer'

        Output:
        ```powershell
        Hello from shared memory!
        ```

        Reads the contents of the memory-mapped file named 'SharedBuffer' and outputs the decoded string.

        .OUTPUTS
        string

        .NOTES
        The UTF-8 decoded string content of the memory-mapped file. Returns null if the file is inaccessible.

        .LINK
        https://psmodule.io/MemoryMapped/Functions/Read-MemoryMappedFileContent
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
        # The unique name for the memory-mapped file.
        [Parameter(Mandatory)]
        [string] $Name
    )

    begin {}

    process {
        try {
            $mmf = [System.IO.MemoryMappedFiles.MemoryMappedFile]::OpenExisting($Name)
        } catch {
            return $null
        }
        $accessor = $mmf.CreateViewAccessor()
        $buffer = New-Object byte[] $accessor.Capacity
        $null = $accessor.ReadArray(0, $buffer, 0, $accessor.Capacity)
        $content = ([System.Text.Encoding]::UTF8.GetString($buffer)).Trim([char]0)
        return $content
    }

    end {}

    clean {
        if ($accessor) {
            $accessor.Dispose()
        }
    }
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [Read-MemoryMappedFileContent] - Done"
#endregion [functions] - [public] - [Read-MemoryMappedFileContent]
#region [functions] - [public] - [Set-MemoryMappedFile]
Write-Debug "[$scriptName] - [functions] - [public] - [Set-MemoryMappedFile] - Importing"
function Set-MemoryMappedFile {
    <#
        .SYNOPSIS
        Creates or returns an existing memory-mapped file by name.

        .DESCRIPTION
        Checks whether a memory-mapped file with the given name already exists. If it does, returns it.
        If not, creates a new memory-mapped file backed by the specified path and size. This function
        ensures idempotent access to shared memory regions through named mapping.

        .EXAMPLE
        Set-MemoryMappedFile -Name 'MySharedMap' -Path 'C:\temp\shared.dat' -Size 2MB

        Output:
        ```powershell
        Capacity : 2097152
        Name : MySharedMap
        SafeMemoryMappedFileHandle : Microsoft.Win32.SafeHandles.SafeMemoryMappedFileHandle
        ```

        Returns a new memory-mapped file named 'MySharedMap' backed by 'C:\temp\shared.dat' with a size of 2MB.

        .OUTPUTS
        System.IO.MemoryMappedFiles.MemoryMappedFile

        .NOTES
        Represents the created or retrieved memory-mapped file object.
        Provides access to shared memory backed by a file with support for named access and reuse.

        .LINK
        https://psmodule.io/MemoryMapped/Functions/Set-MemoryMappedFile/
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'The function only creates a memory-mapped file and does not modify system state in a way that requires confirmation.'
    )]
    [OutputType([System.IO.MemoryMappedFiles.MemoryMappedFile])]
    [CmdletBinding()]
    param(
        # The unique name for the memory-mapped file.
        [Parameter(Mandatory)]
        [string] $Name,

        # Path to the file backing the memory-mapped region.
        [Parameter(Mandatory)]
        [string] $Path,

        # Size in bytes of the memory-mapped file. Defaults to 1MB if not specified.
        [Parameter()]
        [long] $Size = 1MB
    )
    $mmf = Get-MemoryMappedFile -Name $Name
    if ($mmf) {
        return $mmf
    }

    return New-MemoryMappedFile -Name $Name -Path $Path -Size $Size
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [Set-MemoryMappedFile] - Done"
#endregion [functions] - [public] - [Set-MemoryMappedFile]
#region [functions] - [public] - [Set-MemoryMappedFileContent]
Write-Debug "[$scriptName] - [functions] - [public] - [Set-MemoryMappedFileContent] - Importing"
function Set-MemoryMappedFileContent {
    <#
        .SYNOPSIS
        Writes string content into a memory-mapped file with the given name and path.

        .DESCRIPTION
        This function writes UTF-8 encoded content into a memory-mapped file at the specified path.
        It creates or updates the file using the size of the input content. If the -PassThru switch
        is used, the resulting memory-mapped file object is returned. An internal accessor is used
        to perform the write operation, and proper disposal is handled in the clean block.

        .EXAMPLE
        Set-MemoryMappedFileContent -Name "LogMap" -Path "C:\Temp\log.map" -Content "Hello, Memory Map"

        Output:
        ```powershell
        (No output unless -PassThru is specified)
        ```

        Writes "Hello, Memory Map" to a memory-mapped file named 'LogMap' located at 'C:\Temp\log.map'.

        .OUTPUTS
        System.IO.MemoryMappedFiles.MemoryMappedFile

        .NOTES
        Returns the memory-mapped file object when -PassThru is used.

        .LINK
        https://psmodule.io/MemoryMapped/Functions/Set-MemoryMappedFileContent/
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'The function only updates a memory-mapped file.'
    )]
    [OutputType([System.IO.MemoryMappedFiles.MemoryMappedFile])]
    [CmdletBinding()]
    param(
        # The name to assign to the memory-mapped file.
        [Parameter(Mandatory)]
        [string] $Name,

        # The file system path where the memory-mapped file is stored.
        [Parameter(Mandatory)]
        [string] $Path,

        # The string content to write into the memory-mapped file.
        [Parameter(Mandatory, ValueFromPipeline)]
        [string] $Content,

        # If specified, returns the memory-mapped file object after writing.
        [Parameter()]
        [switch] $PassThru
    )

    begin {}

    process {
        Write-Verbose "Setting content for memory-mapped file '$Name'."
        $bytes = [System.Text.Encoding]::UTF8.GetBytes($Content)
        Write-Verbose "Writing content of size $($bytes.Length) bytes to memory-mapped file '$Name'."
        $size = $bytes.Length
        $mmf = Set-MemoryMappedFile -Name $Name -Path $Path -Size $size
        try {
            $accessor = $mmf.CreateViewAccessor()
        } catch {
            throw "Failed to create view accessor for memory-mapped file '$Name'. Exception: $($_.Exception.Message)"
        }
        if (-not $accessor) {
            throw "Failed to create view accessor for memory-mapped file '$Name'."
        }
        $accessor.WriteArray(0, $bytes, 0, $size)
        Write-Verbose "Content written successfully to memory-mapped file '$Name'."
    }

    end {
        if ($PassThru) {
            Write-Verbose "Returning memory-mapped file '$Name'."
            return $mmf
        }
    }

    clean {
        if ($accessor) {
            $accessor.Flush()
            $accessor.Dispose()
        }
        if ($mmf) {
            $mmf.Dispose()
        }
    }
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [Set-MemoryMappedFileContent] - Done"
#endregion [functions] - [public] - [Set-MemoryMappedFileContent]
#region [functions] - [public] - [Show-MemoryMappedFile]
Write-Debug "[$scriptName] - [functions] - [public] - [Show-MemoryMappedFile] - Importing"
function Show-MemoryMappedFile {
    <#
        .SYNOPSIS
        Continuously displays the contents of a memory-mapped file in the console.

        .DESCRIPTION
        This function reads and displays the contents of a specified memory-mapped file in a loop,
        refreshing the view at a user-defined interval. It uses `Read-MemoryMappedFileContent` to
        retrieve data and clears the console after each refresh cycle.

        .EXAMPLE
        Show-MemoryMappedFile -Name 'SharedBuffer' -RefreshSeconds 2

        Output:
        ```powershell
        Data: Hello from another process!
        Timestamp: 2025-06-01T12:00:00
        ```

        Continuously displays the content of the 'SharedBuffer' memory-mapped file, refreshing every 2 seconds.

        .LINK
        https://psmodule.io/MemoryMapped/Functions/Show-MemoryMappedFile
    #>

    [CmdletBinding()]
    param(
        # The name of the memory-mapped file to read and display.
        [Parameter(Mandatory)]
        [string] $Name,

        # The number of seconds to wait between refreshes of the displayed content.
        [Parameter()]
        [int] $RefreshSeconds = 1
    )

    begin {}

    process {
        while ($true) {
            Read-MemoryMappedFileContent -Name $Name
            Start-Sleep -Seconds $RefreshSeconds
            Clear-Host
        }
    }

    end {}
}

#SkipTest:FunctionTest:Will add a test for this function in a future PR
Write-Debug "[$scriptName] - [functions] - [public] - [Show-MemoryMappedFile] - Done"
#endregion [functions] - [public] - [Show-MemoryMappedFile]
Write-Debug "[$scriptName] - [functions] - [public] - Done"
#endregion [functions] - [public]
#region [variables] - [private]
Write-Debug "[$scriptName] - [variables] - [private] - Processing folder"
#region [variables] - [private] - [MemoryMappedFiles]
Write-Debug "[$scriptName] - [variables] - [private] - [MemoryMappedFiles] - Importing"
$script:MemoryMappedFiles = @{}
# TODO: Start by creating a memory-mapped file for holding the file overview.
# TODO: Add logic for other instances of the module to connect to the same memory-mapped file.
Write-Debug "[$scriptName] - [variables] - [private] - [MemoryMappedFiles] - Done"
#endregion [variables] - [private] - [MemoryMappedFiles]
Write-Debug "[$scriptName] - [variables] - [private] - Done"
#endregion [variables] - [private]

#region Member exporter
$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Close-MemoryMappedFile'
        'Get-MemoryMappedFile'
        'New-MemoryMappedFile'
        'Read-MemoryMappedFileContent'
        'Set-MemoryMappedFile'
        'Set-MemoryMappedFileContent'
        'Show-MemoryMappedFile'
    )
    Variable = ''
}
Export-ModuleMember @exports
#endregion Member exporter