MetaNull.MessageQueue.psm1
# Module Constants $User = [Security.Principal.WindowsIdentity]::GetCurrent() $Principal = [Security.Principal.WindowsPrincipal]::new($User) $Role = [Security.Principal.WindowsBuiltInRole]::Administrator if($Principal.IsInRole($Role)) { # Current use is Administrator $PSDriveRoot = 'HKLM:\SOFTWARE\MetaNull\PowerShell\MetaNull.MessageQueue' } else { # Current user is not Administrator $PSDriveRoot = 'HKCU:\SOFTWARE\MetaNull\PowerShell\MetaNull.MessageQueue' } if(-not (Test-Path $PSDriveRoot)) { New-Item -Path $PSDriveRoot -Force | Out-Null } New-Variable MetaNull -Scope script -Value @{ MessageQueue = @{ PSDriveRoot = $PSDriveRoot LockMessageQueue = New-Object Object MutexMessageQueue = New-Object System.Threading.Mutex($false, 'MetaNull.MessageQueue') Drive = New-PSDrive -Name 'MetaNull' -Scope Script -PSProvider Registry -Root $PSDriveRoot } } if(-not (Test-Path MetaNull:\MessageStore)) { # Create the MessageStore directory in the registry # This is where the message details will be stored New-Item -Path MetaNull:\MessageStore -Force | Out-Null } if(-not (Test-Path MetaNull:\MessageQueue)) { # Create the MessageQueue directory in the registry # This is where the message queues will be stored New-Item -Path MetaNull:\MessageQueue -Force | Out-Null } Function Clear-MessageQueue { <# .SYNOPSIS Clears the message queue for a specified queue name. .DESCRIPTION This function clears the message queue for a specified queue name. .PARAMETER MessageQueueId The ID of the message queue to be cleared. .EXAMPLE Clear-MessageQueue -Id '12345678-1234-1234-1234-123456789012' #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Medium')] [OutputType([void])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid]$MessageQueueId ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null Get-Item "MetaNull:\MessageQueue\$MessageQueueId" | Get-ChildItem | Remove-Item | Out-Null } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Find-MessageQueue { <# .SYNOPSIS Find a message queue by Name .DESCRIPTION Find a message queue by Name .PARAMETER Name The name of the message queue .EXAMPLE Find-MessageQueue -Name 'MyQueue' .EXAMPLE Find-MessageQueue -Name 'My*' .OUTPUTS [guid] True if the message queue exists, otherwise false. #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Low')] [OutputType([guid],[Object[]])] param( [Parameter(Mandatory = $false, ValueFromPipeline, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueName @args} )] [SupportsWildcards()] [string]$Name = '*' ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { if(-not $Name) { $Name = '*' } Get-Item "MetaNull:\MessageQueue" | Get-ChildItem | Where-Object { $_ | Get-ItemProperty | Select-Object -ExpandProperty Name | Where-Object { $_ -like $Name } } | Select-Object -ExpandProperty PSChildName | ForEach-Object { [guid]::new($_) } } finally { $ErrorActionPreference = $BackupErrorActionPreference } } } Function Get-Message { <# .SYNOPSIS Get the messages in a message queue. .DESCRIPTION Get the messages in a message queue. .PARAMETER MessageQueueId The ID of the message queue to be retrieved. .EXAMPLE Get-MessageQueue -Id '12345678-1234-1234-1234-123456789012' #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Low')] [OutputType([Object],[Object[]])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid]$MessageQueueId ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null # Get the messages in the queue $Messages = Get-Item -Path "MetaNull:\MessageQueue\$MessageQueueId" | Get-ChildItem | Get-ItemProperty $Messages | Sort-Object -Property Index | Foreach-Object { $Message = $_ Get-Item -Path "MetaNull:\MessageStore\$($Message.MessageId)" | Get-ItemProperty | Select-Object -Property @( @{Name = 'MessageQueueId'; Expression = { $MessageQueueId }} @{Name = 'MessageId'; Expression = { [guid]($Message.MessageId) }} @{Name = 'Index'; Expression = { [int]$Message.Index }} @{Name = 'Label'; Expression = { $_.Label }} @{Name = 'Date'; Expression = { [datetime]($_.Date | ConvertFrom-Json) }} @{Name = 'MetaData'; Expression = { $_.MetaData | ConvertFrom-Json }} ) | Write-Output } } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Get-MessageQueue { <# .SYNOPSIS Get a message queue. .DESCRIPTION This function gets a message queue. .PARAMETER Id The ID of the message queue to be retrieved. This is a mandatory parameter and must be provided. .EXAMPLE Get-MessageQueue -Id '12345678-1234-1234-1234-123456789012' #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Low')] [OutputType([Object],[Object[]])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid]$MessageQueueId ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null # Get the message queue and its properties Get-ItemProperty -Path "MetaNull:\MessageQueue\$MessageQueueId" | Select-Object -ExcludeProperty PS* } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function New-MessageQueue { <# .SYNOPSIS Create a new message queue. .DESCRIPTION This function creates a new message queue. .PARAMETER Name The name of the message queue to be created. .PARAMETER MaximumSize The maximum size of the message queue. This is an optional parameter and defaults to 100 messages. .PARAMETER MessageRetentionPeriod The message retention period in days. This is an optional parameter and defaults to 7 days. .EXAMPLE New-MessageQueue -Name 'MyQueue' -MaximumSize 100 -MessageRetentionPeriod 7 .OUTPUTS [guid] The ID of the newly created message queue. #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Medium')] [OutputType([guid])] param( [Parameter(Mandatory, Position = 0)] [string]$Name, [Parameter(Mandatory = $false, Position = 1)] [ValidateRange(1, 10000)] [int]$MaximumSize = 100, [Parameter(Mandatory = $false, Position = 2)] [ValidateRange(1, 365)] [int]$MessageRetentionPeriod = 7 ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null # Check if the queue already exists if(Find-MessageQueue -Name $Name) { throw "A MessageQueue with name $Name already exists." } # Create the message queue $MessageQueueId = New-Guid $Item = New-Item -Path "MetaNull:\MessageQueue\$MessageQueueId" $Item | Set-ItemProperty -Name 'MessageQueueId' -Value $MessageQueueId $Item | Set-ItemProperty -Name 'Name' -Value $Name $Item | Set-ItemProperty -Name 'MaximumSize' -Value $MaximumSize $Item | Set-ItemProperty -Name 'MessageRetentionPeriod' -Value $MessageRetentionPeriod return $MessageQueueId } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Optimize-MessageQueues { <# .SYNOPSIS Clears the message queue from outdated or excess messages. .DESCRIPTION This function clears the message queue from outdated or excess messages. #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Medium')] [OutputType([void])] param() Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null $CurrentDate = (Get-Date) $Queues = Get-Item "MetaNull:\MessageQueue" | Get-ChildItem $Queues | Foreach-Object { Write-Verbose "Processing MessageQueue $($_.PSChildName)" $Properties = $_ | Get-ItemProperty # Remove Excess Messages if($Properties.MaximumSize -gt 0) { $Children = $_ | Get-ChildItem | Sort-Object { $_ | Get-ItemProperty | Select-Object -ExpandProperty Index } if($Children.Count -gt $Properties.MaximumSize) { $ExcessMessages = $Children.Count - $Properties.MaximumSize Write-Verbose "Removing $ExcessMessages excess message(s) from MessageQueue" $Children | Select-Object -First $ExcessMessages | Remove-Item } } # Remove Outdated Messages if($Properties.MessageRetentionPeriod -gt 0) { $DateConstraint = $CurrentDate.AddDays(-$Properties.MessageRetentionPeriod) $Children = $_ | Get-ChildItem | Where-Object { $DateConstraint -gt ([datetime]($_ | Get-ItemProperty | Select-Object -ExpandProperty Date | ConvertFrom-Json)) } if($Children.Count -gt 0) { $ExcessMessages = $Children.Count Write-Verbose "Removing $ExcessMessages outdated message(s) from MessageQueue" $Children | Remove-Item } } } # Remove leftover messages from the MessageStore $MessageIdList = Get-Item "MetaNull:\MessageQueue" | Get-ChildItem | Get-ChildItem | Get-ItemProperty | Select-Object -ExpandProperty MessageId | Select-Object -unique $Children = Get-Item "MetaNull:\MessageStore" | Get-ChildItem | Where-Object { $_.PSChildName -notin $MessageIdList } if($Children.Count -gt 0) { $ExcessMessages = $Children.Count Write-Verbose "Removing $ExcessMessages unlinked message(s) from MessageStore" $Children | Remove-Item } } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Pop-Message { <# .SYNOPSIS Get and remove the oldest message in a message queue. .DESCRIPTION Get and remove the oldest message in a message queue. .PARAMETER MessageQueueId The ID of the message queue(s) where message should be added to. .EXAMPLE Pop-Message -Id '12345678-1234-1234-1234-123456789012' #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Low')] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid[]]$MessageQueueId ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null # Get the one oldest message in the queue $Messages = Get-Item -Path "MetaNull:\MessageQueue\$MessageQueueId" | Get-ChildItem | Get-ItemProperty $MessageQueueItem = $Messages | Sort-Object -Property Index | Select-Object -First 1 | Foreach-Object { $Message = $_ Get-Item -Path "MetaNull:\MessageStore\$($Message.MessageId)" | Get-ItemProperty | Select-Object -Property @( @{Name = 'MessageQueueId'; Expression = { $MessageQueueId }} @{Name = 'MessageId'; Expression = { [guid]($Message.MessageId) }} @{Name = 'Index'; Expression = { [int]$Message.Index }} @{Name = 'Label'; Expression = { $_.Label }} @{Name = 'Date'; Expression = { [datetime]($_.Date | ConvertFrom-Json) }} @{Name = 'MetaData'; Expression = { $_.MetaData | ConvertFrom-Json }} ) } # Remove the message from the message queue (but not from the message store) Get-Item -Path "MetaNull:\MessageQueue\$MessageQueueId" | Get-ChildItem | Where-Object { $_.PSChildName -eq $MessageQueueItem.Index } | Remove-Item # Return the message $MessageQueueItem | Write-Output } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Push-Message { <# .SYNOPSIS Adds a message to one or several message queue(s). .DESCRIPTION Adds a message to one or several message queue(s). .PARAMETER MessageQueueId The ID of the message queue(s) where message should be added to. .PARAMETER Label The label of the message. .PARAMETER MetaData The metadata of the message. .OUTPUTS [int] the index of the added message in the queue .EXAMPLE Push-Message -Id '12345678-1234-1234-1234-123456789012' -Label 'Test' -MetaData @{ Test = 'Test' } #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Low')] [OutputType([int])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid[]]$MessageQueueId, [Parameter(Mandatory, Position = 1)] [string]$Label, [Parameter(Mandatory = $false, Position = 2)] [AllowNull()] [object]$MetaData = $null ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null $MessageData = [pscustomobject]@{ MessageId = [guid]::NewGuid() Index = 1 Label = $Label Date = ((Get-Date)|ConvertTo-JSon) MetaData = ($MetaData|ConvertTo-JSon) } # Add the message to the message store $MessageStore = New-Item -Path "MetaNull:\MessageStore\$($MessageData.MessageId)" $MessageStore | Set-ItemProperty -Name 'MessageId' -Value $MessageData.MessageId $MessageStore | Set-ItemProperty -Name 'Label' -Value $MessageData.Label $MessageStore | Set-ItemProperty -Name 'Date' -Value $MessageData.Date $MessageStore | Set-ItemProperty -Name 'MetaData' -Value $MessageData.MetaData # Add the reference to the message in each messagequeue $MessageQueueId | Foreach-Object { $MQID = $_ $MessageData.Index = 1 # Find the highest index in THIS queue $Item = Get-Item -Path "MetaNull:\MessageQueue\$MQID" $NewIndex = $Item | Get-ChildItem | Get-ItemProperty | Sort-Object -Property Index | Select-Object -Last 1 | ForEach-Object { $_.Index + 1 } if($NewIndex -ne $null) { $MessageData.Index = $NewIndex } # Add the reference to THIS messagequeue $Message = New-Item -Path "MetaNull:\MessageQueue\$MQID\$($MessageData.Index)" $Message | Set-ItemProperty -Name 'MessageId' -Value $MessageData.MessageId $Message | Set-ItemProperty -Name 'Index' -Value $MessageData.Index $Message | Set-ItemProperty -Name 'Date' -Value $MessageData.Date # return the message id return $MessageData.Index } } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Remove-MessageQueue { <# .SYNOPSIS Remove a message queue. .DESCRIPTION This function removes the whole message queue. .PARAMETER MessageQueueId The ID of the message queue to be removed. .EXAMPLE Remove-MessageQueue -Id '12345678-1234-1234-1234-123456789012' #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Medium')] [OutputType([void])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid]$MessageQueueId ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { $MetaNull.MessageQueue.MutexMessageQueue.WaitOne() | Out-Null # Remove the message queue Remove-Item "MetaNull:\MessageQueue\$MessageQueueId" -Recurse } finally { $MetaNull.MessageQueue.MutexMessageQueue.ReleaseMutex() $ErrorActionPreference = $BackupErrorActionPreference } } } Function Resolve-MessageQueueId { <# .SYNOPSIS Lookup for valid Message Queue Ids, and provides Auto Completion for partial Ids .PARAMETER $PartialId A partial Message Queue ID .EXAMPLE # Direct use $IDs = Resolve-MessageQueueId -PartialId '123*' .EXAMPLE # Use as a Parameter Argument Completer Function MyFunction { param( [Parameter(Mandatory)] [SupportsWildcards()] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid] $Id = [guid]::Empty, ) "Autocompleted ID: $Id" } #> [CmdletBinding(DefaultParameterSetName = 'ArgumentCompleter')] param ( [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $commandName, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $parameterName, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $wordToComplete, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $commandAst, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $fakeBoundParameters, [Parameter(Mandatory,ParameterSetName = 'Lookup')] [SupportsWildcards()] $PartialId ) $PartialQueueId = '*' if($PSCmdlet.ParameterSetName -eq 'ArgumentCompleter') { $PartialQueueId = "$wordToComplete*" } elseif($PSCmdlet.ParameterSetName -eq 'Lookup') { $PartialQueueId = "$PartialId" } else { throw "Invalid ParameterSet" } Get-ChildItem -Path "MetaNull:\MessageQueue" | Split-Path -Leaf | Where-Object { $_ -like $PartialQueueId } } Function Resolve-MessageQueueName { <# .SYNOPSIS Lookup for valid Queue Names, and provides Auto Completion for partial Names .PARAMETER $PartialName A partial MEssage Queue Name .EXAMPLE # Direct use $IDs = Resolve-MessageQueueName -PartialName 'Queue*' .EXAMPLE # Use as a Parameter Argument Completer Function MyFunction { param( [Parameter(Mandatory)] [SupportsWildcards()] [ArgumentCompleter( {Resolve-MessageQueueName @args} )] [string] $Name ) "Autocompleted Name: $Name" } #> [CmdletBinding(DefaultParameterSetName = 'ArgumentCompleter')] param ( [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $commandName, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $parameterName, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $wordToComplete, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $commandAst, [Parameter(Mandatory,ParameterSetName = 'ArgumentCompleter')] $fakeBoundParameters, [Parameter(Mandatory,ParameterSetName = 'Lookup')] [SupportsWildcards()] $PartialName ) $PartialQueueName = '*' if($PSCmdlet.ParameterSetName -eq 'ArgumentCompleter') { $PartialQueueName = "$wordToComplete*" } elseif($PSCmdlet.ParameterSetName -eq 'Lookup') { $PartialQueueName = "$PartialName" } else { throw "Invalid ParameterSet" } Get-ChildItem -Path "MetaNull:\MessageQueue" | Get-ItemProperty | Select-Object -ExpandProperty Name | Where-Object { $_ -like $PartialQueueName } } Function Test-MessageQueue { <# .SYNOPSIS Test if a Message Queue exists .DESCRIPTION Test if a Message Queue exists .PARAMETER MessageQueueId The Id of the message queue to be retrieved. .PARAMETER Name Lookup by Name rather than by Id. .EXAMPLE Test-MessageQueue -Id '12345678-1234-1234-1234-123456789012' .EXAMPLE Test-MessageQueue -Name 'MyQueue' .OUTPUTS [bool] True if the message queue exists, otherwise false. #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'Low')] [OutputType([bool])] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [ArgumentCompleter( {Resolve-MessageQueueId @args} )] [guid]$MessageQueueId ) Process { $BackupErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { return Test-Path "MetaNull:\MessageQueue\$MessageQueueId" } finally { $ErrorActionPreference = $BackupErrorActionPreference } } } |