Mutex.psm1

function Get-Mutex {
    <#
    .SYNOPSIS
        Get currently defined Mutexes.
     
    .DESCRIPTION
        Get currently defined Mutexes.
        Only returns mutexes owned and managed by this module.
     
    .PARAMETER Name
        Name of the mutex to retrieve.
        Supports wildcards, defaults to '*'
     
    .EXAMPLE
        PS C:\> Get-Mutex
 
        Return all mutexes.
 
    .EXAMPLE
        PS C:\> Get-Mutex -Name MyModule.LogFile
 
        Returns the mutex named "MyModule.LogFile"
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Name = '*'
    )
    
    process {
        $script:mutexes.Values | Where-Object Name -like $Name
    }
}


function Lock-Mutex {
    <#
    .SYNOPSIS
        Acquire a lock on a mutex.
     
    .DESCRIPTION
        Acquire a lock on a mutex.
        Implicitly calls New-Mutex if the mutex hasn't been taken under the management of the current process yet.
     
    .PARAMETER Name
        Name of the mutex to acquire a lock on.
     
    .PARAMETER Timeout
        How long to wait for acquiring the mutex, before giving up with an error.
     
    .EXAMPLE
        PS C:\> Lock-Mutex -Name MyModule.LogFile
 
        Acquire a lock on the mutex 'MyModule.LogFile'
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Name,

        [timespan]
        $Timeout
    )

    process {
        foreach ($mutexName in $Name) {
            if (-not $script:mutexes[$mutexName]) { New-Mutex -Name $mutexName }
            if (-not $Timeout) { $script:mutexes[$mutexName].Object.WaitOne() }
            else {
                try { $script:mutexes[$mutexName].Object.WaitOne($Timeout) }
                catch {
                    Write-Error $_
                    continue
                }
            }
            $script:mutexes[$mutexName].Status = 'Locked'
        }
    }
}


function New-Mutex {
    <#
    .SYNOPSIS
        Create a new mutex managed by this module.
     
    .DESCRIPTION
        Create a new mutex managed by this module.
        The mutex is created in an unacquired state.
        Use Lock-Mutex to acquire the mutex.
 
        Note: Calling Lock-Mutex without first calling New-Mutex will implicitly call New-Mutex.
         
    .PARAMETER Name
        Name of the mutex to create.
        The name is what the system selects for when marshalling access:
        All mutexes with the same name block each other, across all processes on the current host.
     
    .EXAMPLE
        PS C:\> New-Mutex -Name MyModule.LogFile
 
        Create a new, unlocked mutex named 'MyModule.LogFile'
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [string]
        $Name
    )
    
    process {
        if ($script:mutexes[$Name]) { return }
        $script:mutexes[$Name] = [PSCustomObject]@{
            Name   = $Name
            Status = "Open"
            Object = [System.Threading.Mutex]::new($false, $Name)
        }
    }
}


function Remove-Mutex {
    <#
    .SYNOPSIS
        Removes a mutex from the list of available mutexes.
     
    .DESCRIPTION
        Removes a mutex from the list of available mutexes.
        Only affects mutexes owned and managed by this module.
        Will silently return on unknown mutexes, not throw an error.
     
    .PARAMETER Name
        Name of the mutex to remove.
        Must be an exact, case-insensitive match.
     
    .EXAMPLE
        PS C:\> Get-Mutex | Remove-Mutex
 
        Clear all mutex owned by the current runspace managed by this module.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]
        $Name
    )
    
    process {
        foreach ($mutexName in $Name) {
            if (-not $script:mutexes[$mutexName]) { continue }
            Unlock-Mutex -Name $mutexName
            $script:mutexes.Remove($mutexName)
        }
    }
}


function Unlock-Mutex {
    <#
    .SYNOPSIS
        Release the lock on a mutex you manage.
     
    .DESCRIPTION
        Release the lock on a mutex you manage.
        Will silently return if the mutex does not exist.
     
    .PARAMETER Name
        The name of the mutex to release the lock on.
     
    .EXAMPLE
        PS C:\> Unlock-Mutex -Name MyModule.LogFile
 
        Release the lock on the mutex 'MyModule.LogFile'
 
    .EXAMPLE
        PS C:\> Get-Mutex | Release-Mutex
 
        Release the lock on all mutexes managed.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $Name
    )
    
    process {
        foreach ($mutexName in $Name) {
            if (-not $script:mutexes[$mutexName]) { return }
            if ($script:mutexes[$mutexName].Status -eq "Open") { return }
            $script:mutexes[$mutexName].Object.ReleaseMutex()
            $script:mutexes[$mutexName].Status = 'Open'
        }
    }
}

# Central list of all mutexes
$script:mutexes = @{ }