2
votes

I have a function that is used to purge a message queue on a machine but I'm looking to adapt it and make it a little more robust. I'd like to be able to fire the command to a machine even if the current machine doesn't have MSMQ installed.

The local command works without issue but when the invoke-command is called, the check to see if the queue exists returns false (even though the queue does exist). Has anyone run into anything like this before? Any suggestions?

This is my function:

Function Purge-MessageQueue
{
<#
.Synopsis
   Use hostname to purge message queue
.DESCRIPTION
   Checks if MSMQ is locally installed otherwise fire the purge
   command to the machine you are purging
.EXAMPLE
   Purge-MessageQueue -targetMachine $env:computername -queueName 'test'
#>
Param
(
[Parameter(Mandatory=$true)]
    [String]$targetMachine,
[Parameter(Mandatory=$true)]
    [string]$queueName
)
Write-Verbose "Purging $queueName queue on: $targetMachine"
$queueName = "$targetMachine\$queueName"

$error.clear()
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
try {[void][System.Messaging.MessageQueue]::Exists($queueName)}
catch
{
    if ($_.exception.ToString() -like '*Message Queuing has not been installed on this computer.*') 
    {
        #push command to machine
        $RemoteSuccess = Invoke-Command -ComputerName $targetMachine -ScriptBlock { Param($queueName)
            [void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
            If([System.Messaging.MessageQueue]::Exists($queueName)) 
            {
                $queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
                Try{$queue.Purge()}
                Catch{$error}
            }
        } -ArgumentList $queueName
    }
}
If(!$error)
{
    If([System.Messaging.MessageQueue]::Exists($queueName)) 
    {
        $queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
        $queue.Purge()
    }
}
If(!$Error -and !$RemoteSuccess)
{
    Write-Host "$queueName queue on $targetMachine cleared"
}
Else
{
    Write-Warning "Failed locating queue $queueName on $targetMachine" 
}
}

NOTES: In order to identify what exactly is going on, I used write-host on the exists statement and it returns false. The queue is not being found when I pass the scriptblock. It is executing on the other machine (tested writing a file which succeeded). When I run:

Write-Host "$([System.Messaging.MessageQueue]::Exists($queueName))`n$queueName"
$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName

I get the false, the correct queue name, and the following error:

Exception calling "Purge" with "0" argument(s): "The queue does not exist or you do not have sufficient permissions to perform the operation." + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : MessageQueueException + PSComputerName : XXXXXX

Running the same command directly on the machine works without issue.

I also found someone else trying to do something similar on serverfault: https://serverfault.com/questions/399178/how-to-retrieve-names-of-all-private-msmq-queues-efficiently

And when I try this:

Invoke-Command -ComputerName $targetMachine -ScriptBlock { Get-MsmqQueue }

I get the following result:

Cannot find specified machine. + CategoryInfo : ObjectNotFound: (:) [Get-MsmqQueue], MessageQueueException + FullyQualifiedErrorId : MachineNotFound,Microsoft.Msmq.PowerShell.Commands.GetMSMQQueueCommand

This following command does return the data, but it doesn't allow me to send a purge command:

Invoke-Command -ComputerName $targetMachine -ScriptBlock {Get-WmiObject -class Win32_PerfRawData_MSMQ_MSMQQueue}

I also tried to write the content to a script file and then call the file, which when run on the machine, works without issue but not when called via invoke-command:

    $filewriter = @"
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists('$queueName')) 
{
    `$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
    Try{`$objqueue.Purge()}
    Catch{`$error}
}
"@
            $session = New-PSSession -ComputerName $targetMachine 
            Invoke-Command -Session $session -ScriptBlock {Param($FileWriter)
                $FileWriter | Out-File "C:\Temp\PurgeQueue.ps1"
            } -ArgumentList $filewriter
            $test = Invoke-Command -Session $session -scriptblock {Pushd "C:\Temp\"
                .\PurgeQueue.ps1}
2

2 Answers

4
votes

I have not found the cause for this, but I will summarize what I have found and my workaround

Summary: When invoking msmq commands via invoke-command, only private queues appear and can be manipulated.

Workaround: I've build a function to deal with purging and adding message to queues by creating scheduled tasks on the remote machine to call the script created by the command.

Function Push-MSMQRemoteCommand
{
    Param(
        [Parameter(Mandatory=$true)]
        $targetMachine,
        [Parameter(Mandatory=$true)]
        $password,
        [Parameter(Mandatory=$true)]
        $queueName,
        [Switch]$purge,
        $user,
        $message)
    Begin
    {
        If(!$user){$user = "$env:USERDOMAIN\$env:USERNAME"}
        If($purge -and $message.Length -ne 0){Write-Error "Choose to purge or add... not both" -ErrorAction Stop}

        $queuepath = "$targetMachine\$queueName"
        #build commands to push
        If($purge)
        {
            $scriptblock = @"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If ([System.Messaging.MessageQueue]::Exists('$queuePath')) {
`$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queuePath
`$queue.Purge()
}
"@
        }
        ElseIf($message)
        {
            If($message.Length -eq 0){Write-Error "No message provided to add message" -ErrorAction Stop}
            $scriptblock = @"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
`$queue = new-object System.Messaging.MessageQueue "$queuepath"
`$utf8 = new-object System.Text.UTF8Encoding

`$msgBytes = `$utf8.GetBytes('$message')

`$msgStream = new-object System.IO.MemoryStream
`$msgStream.Write(`$msgBytes, 0, `$msgBytes.Length)

`$msg = new-object System.Messaging.Message
`$msg.BodyStream = `$msgStream
`$msg.Label = "RemoteQueueManagerPowershell"
`$queue.Send(`$msg)
"@
        }

        #Push Commands
        Invoke-Command -ComputerName $targetMachine -ScriptBlock {
            Param($user,$password,$scriptblock)
            $scriptblock | Out-file -FilePath "C:\temp\ManageQueue.ps1" -Force
            $action = New-ScheduledTaskAction -execute 'powershell.exe' -Argument '-File "C:\temp\ManageQueue.ps1"'
            #scheudling action to start 2 seconds from now
            $trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date)+(New-TimeSpan -Seconds 2))
            Register-ScheduledTask -TaskName RemoteQueueManager `
                               -Action $action `
                               -Trigger $trigger `
                               -User "$user"`
                               -Password $password
            #Start-Sleep -Seconds 10
            Unregister-ScheduledTask -TaskName RemoteQueueManager -Confirm:$false
            Remove-Item -Path "C:\temp\ManageQueue.ps1" -Force
        } -ArgumentList $user,$password,$scriptblock
    }
}
1
votes

From your analysis I have feeling that it is issue of rights.

Did you check the rights for your user?

If you are a normal user you have to do the following (not an Administrator) on the destination computer/server/VM:

1) first create a group and add there users

net localgroup "Remote PowerShell Session Users" /add
net localgroup "Remote PowerShell Session Users" the-user /add

2) Invoke GUI

Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI

3) Add Remote PowerShell Session Users group and grant it execute (invoke) rights

4) Restart the service:

Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI

5) the user now should be able to run remote session

The original source is here.