2

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}
Xanderu
  • 747
  • 1
  • 8
  • 30

2 Answers2

4

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
    }
}
Xanderu
  • 747
  • 1
  • 8
  • 30
1

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.

tukan
  • 17,050
  • 1
  • 20
  • 48
  • The user is a local admin for the machine. I'm able to fire other commands that require elevated permissions (such as start or stop service or process). I don't see permission errors on any other commands. I also am able to invoke the commands without error, but no results are returned. When I use the the following command I can see that the queue is there: Invoke-Command -ComputerName $targetMachine -ScriptBlock { gwmi -class Win32_PerfRawData_MSMQ_MSMQQueue } – Xanderu Feb 28 '18 at 16:51
  • @jewtus So we need to have further debugging. Did you try to use `wireshark` on the workstation to see if anything is going out? On Server that you see the command incoming? – tukan Mar 01 '18 at 14:48
  • I'm honestly not very familiar with wireshark (I tried to download and install but I'm not sure how to setup the actual monitoring). I did run PROCMON on the remote machine and I can see the script (C:\Temp\PurgeQueue.ps1) running on the machine if I write the command to a file and invoke the file on remote machine. No errors are recorded/captured and the queue is still not cleared out. – Xanderu Mar 01 '18 at 16:42
  • @Jewtus I see, well wireshark can be challenging if you did not use it. If you see it via **procmon** that should be enough for now. What powershell versions are on the client and what on the server? – tukan Mar 02 '18 at 08:28
  • To be clear I'm asking if the powershell versions are same for both? – tukan Mar 02 '18 at 09:46
  • Primary machine: 5.1.14393.1884 | Remote machine 4.0| I also noticed something unusual when I was messing with it. It looks as though get-MSMQqueue does actually get the PRIVATE messages queues when I'm not providing a filter. Nothing I try seems to find the public queues. Both machines are on the same domain and connected via the same DC using the same user profile so I can't image it has something to do with the MSMQ AD component... – Xanderu Mar 02 '18 at 14:51
  • @Jewtus could you try it with the primary machine being on 4.x or the remote machine on 5.x one of these? – tukan Mar 02 '18 at 14:53
  • @Jewtus my suspicion is that the error maybe cause by the PS version incompatibilities. – tukan Mar 02 '18 at 15:13
  • I upgraded the version of powershell on the remote machine and tried again but I had the same result. When I run Get-msmqqueue via invoke command it returns the private queues after the following error: Cannot find specified machine. + CategoryInfo : ObjectNotFound: (:) [Get-MsmqQueue], MessageQueueException + FullyQualifiedErrorId : MachineNotFound,Microsoft.Msmq.PowerShell.Commands.GetMSMQQueueCommand + PSComputerName : – Xanderu Mar 02 '18 at 15:14
  • @Jewtus did you restart the machine after the upgrade? – tukan Mar 02 '18 at 15:25
  • yes, I did. I also tested the command again from on the remote machine and it works without issue. I'm going to try to work around this with PSEXEC or creating a task to run immediately to see if that will work. At this point I think it might be a bug... Everything else seems to work but the MSMQ functions. – Xanderu Mar 02 '18 at 15:27
  • @Jewtus probably a bug. You could debug a remote session like done here - https://blogs.technet.microsoft.com/heyscriptingguy/2013/11/17/remote-script-debugging-in-windows-powershell/ – tukan Mar 02 '18 at 19:43
  • @Jewtus last thing that came into my mind running New-Object -TypeName System.Diagnostics.ProcessStartInfo instead of invoke. – tukan Mar 02 '18 at 19:46