Cyber Security

Active Directory: Send Messages to all currently logged on Users (msg.exe)

Do you remember the net send command? And do you remember the security concerns? Last week I played with msg.exe. Msg is the “new” net send. I tried to send a message to all users and computers in my domain. Why? I wanted to instruct all users to close all open programs. And now I want to keep and share this knowledge in form of this blog post.

Introduction (msg.exe)

Msg sends a message to a user. This user must be logged in as a domain user (Domain Profile). Which means that the following only works in a domain environment. For network technicans: msg uses Port 445 (SMB/CIFS). If you send a message to a user you have to provide a computername and a username. Or you can try sending a message to yourself:


msg * "Hallo, this is a test!"

1.PNG

* means, that the message is send to all logged on users. Keep in mind that Windows is a multi-user operating system. Don’t forget the others! 😉

Prerequisites

The headline is somewhat misleading. Yes, we are going to send messages to all users, but actually to all computers. Suppose all your client computers are stored in specific Organizational Unit called Workstations. You want to inform all users logged on to this computers. I am going to use Invoke-Command. Make sure, your client computers accepts Remote PowerShell commands. You can run Enable-PSRemoting on each of them. Or you could configure WinRM by using Group Policies:

Group Policies: Enabling WinRM for Windows Client Operating Systems (Windows 10, Windows 8, Windows 7)

Summary

  • All Computers must reside in the same domain
  • WinRM has to be enabled on the client computers by running Enable-PSRemoting or by configuring via Group Policies, as described in the link above. Note that on Windows Server 2012/2016 operating systems WinRM is enabled by default, but not on Windows Client systems.

Sending Messages to all Users: PowerShell and Msg.exe in Action

The following One-Liner gets all the computernames of the OU Workstations in the domain sid-500.com. Afterwards msg is executed for each computer in the OU Workstations. The message is send to all users which are currently logged on.


(Get-ADComputer -SearchBase "OU=Workstations,DC=sid-500,DC=com" -Filter *).Name | Foreach-Object {Invoke-Command -ComputerName $_ {msg * "Please close all open files. The Server will be shut down in 5 Minutes"}}

1.PNG

Client01 is a member of the OU Workstation. Client01 receives the message.

1.PNG

Have fun playing with msg!

See also

For more remote actions see also:

PowerShell: Enable Remote Desktop on multiple Servers remotely (Bulk)

PowerShell: Using Restart-Computer to restart your Computer and Remote Computers

43 replies »

  1. I’m trying to use a variable for msg but it’s erroring out – see below:

    #write-host $x
    (Get-ADComputer -SearchBase “OU=Test_Devices,OU=Test_OU,OU=Dept OU,DC=crmc,DC=health” -Filter *).Name | Foreach-Object {Invoke-Command -ComputerName $_ {msg * $x}}

    Error message:

    Message from STDIN is empty; no message sent
    + CategoryInfo : NotSpecified: (Message from ST…no message sent:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
    + PSComputerName : DESKTOP-U1NMD2N

    Any suggestions?

    Like

  2. I know this is an old thread, but just in case anyone still monitors it, there is a way to speed up the execution of this script.

    First, add an IF loop to test if the computer responds to a ping.

    Second, use the new multithread option -Parallel available in Powershell 7.

    To do this, you need to Install Powershell 7. Just follow:

    https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows?view=powershell-7.1#installing-the-msi-package

    I use it as follows and run it from the server I am restarting:

    (Get-ADComputer -SearchBase “OU=Workstations,DC=blah,DC=blah” -Filter *).Name |
    ForEach-Object -Parallel {
    IF (Test-Connection $_ -Quiet) {
    Invoke-Command -ComputerName $_ {msg * “The server will restart in 5 minutes. Please save and close all open files to prevent data loss.”}
    }
    }
    Start-Sleep -Seconds 300 ; Restart-Computer -Force

    Like

      • I’m trying to use this same method to run an executable which is located on a shared network location.

        I need it to run on each workstation.

        Below is what I have, but I am getting

        Start-Process: Acces is denied

        (Get-ADComputer -SearchBase “OU=Workstations,DC=dynamic-structures,DC=us” -Filter *).Name |
        ForEach-Object -Parallel {
        IF (Test-Connection $_ -Quiet) {
        Invoke-Command -Computername $_ -ScriptBlock {Start-Process ‘<network path to exe' -WorkingDirectory 'c:\windows\temp\' -ArgumentList '/s' -Wait}
        }
        }

        Any help would be greatly appreciated.

        Like

      • It’s difficult to help you in this case, because the code looks good but the error message is clear. You have insufficient rights. Check your account. Domain Admin? Best, P

        Like

    • Sorry – I was just using that for debugging did not mean to cut and paste it in. I’m trying to create a GUI for this for mass messaging all computers in the organization. $x is created by user input by typing into a dialog box

      This is full script that:

      #load GUI
      Add-Type -AssemblyName System.Windows.Forms
      Add-Type -AssemblyName System.Drawing

      #form
      $form = New-Object System.Windows.Forms.Form
      $form.Text = ‘Data Entry Form’
      $form.Size = New-Object System.Drawing.Size(300,200)
      $form.StartPosition = ‘CenterScreen’

      $okButton = New-Object System.Windows.Forms.Button
      $okButton.Location = New-Object System.Drawing.Point(75,120)
      $okButton.Size = New-Object System.Drawing.Size(75,23)
      $okButton.Text = ‘OK’
      $okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
      $form.AcceptButton = $okButton
      $form.Controls.Add($okButton)

      $cancelButton = New-Object System.Windows.Forms.Button
      $cancelButton.Location = New-Object System.Drawing.Point(150,120)
      $cancelButton.Size = New-Object System.Drawing.Size(75,23)
      $cancelButton.Text = ‘Cancel’
      $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
      $form.CancelButton = $cancelButton
      $form.Controls.Add($cancelButton)

      $label = New-Object System.Windows.Forms.Label
      $label.Location = New-Object System.Drawing.Point(10,20)
      $label.Size = New-Object System.Drawing.Size(280,20)
      $label.Text = ‘Please enter the information in the space below:’
      $form.Controls.Add($label)

      $textBox = New-Object System.Windows.Forms.TextBox
      $textBox.Location = New-Object System.Drawing.Point(10,40)
      $textBox.Size = New-Object System.Drawing.Size(260,20)
      $form.Controls.Add($textBox)

      $form.Topmost = $true

      $form.Add_Shown({$textBox.Select()})
      $result = $form.ShowDialog()

      if (($result -eq [System.Windows.Forms.DialogResult]::OK))
      {
      $x = $textBox.Text
      }

      (Get-ADComputer -SearchBase “OU=Test_Devices,OU=Test_OU,OU=Dept OU,DC=domain,DC=com” -Filter *).Name | Foreach-Object {Invoke-Command -ComputerName $_ {msg * $x}}

      Like

      • (Get-ADComputer -SearchBase “OU=TestOU,DC=test,DC=com” -Filter *).Name | Foreach-Object {Invoke-Command -ComputerName $_ {msg * $using:x}}

        I had to add $usingx instead of $x to get the variable to work.

        Per

        “PowerShell script blocks are not lexical closures by default. The scriptblock passed to Invoke-Command does not save the current value of the $Message parameter when being run on the other computer.

        When the block is run in the remote session, it is using the current value of $Message in that session. Because that variable is most likely $null, the message is omitted from your command.”

        Thanks Patrick for your help and your wonderful script.

        Like

  3. Hello,

    I have tried from a PS with admin rights in one machine from domain, but I receive the following message:

    Get-ADComputer : The term ‘Get-ADComputer’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Read several cases about this but still I didnt figure out, how to solve it. Do you know how can be resolved?

    Thanks in advance!

    Like

  4. i have tested it in my test environment and it is working very well, the issue is on my production environment which has a lot of computer accounts, how to i make the script run faster because it takes a very long time for the script to get executed.

    Like

  5. The Script Worked perfectly in my Test Environment, but in the Production Environment it takes too Long to get executed. is there anything to help to quicken it?

    Like

  6. Hello
    If the computer is turned on we receive error messages
    [PC1] Connecting to remote server PC1 failed with the following error message: WinRM service can not
    finish the operation. Check whether the computer name provided is correct and whether the computer is available on the network and whether the exception is
    firewall for the WinRM service is enabled and allows access from this computer. By default, the firewall exception for the WinRM service in
    If there are public profiles, restricts access to remote computers that are on the same local subnet. For more
    information, see the about_Remote_Troubleshooting Help topic.
    + CategoryInfo: OpenError: (PC1: String) [], PSRemotingTransportException
    + FullyQualifiedErrorId: WinRMOperationTimeout, PSSessionStateBroken
    How to send on computers only on-line?

    Like

      • Yes everything is set correctly. If the PC is an online message appears. If the PC is offline it receives an error in the script as I wrote. It is very slow to run as there is 100 PC in the network

        Like

  7. not working.
    I run this command in power shell:

    (Get-ADComputer -SearchBase “OU=HR,DC=mydomaint,DC=com” -Filter *).Name | Foreach-Object {Invoke-Command -ComputerName $_ {msg * “This is Test Message”}}

    got this error

    Invoke-Command : Cannot validate argument on parameter ‘ScriptBlock’. The argument is null. Provide a valid value for the argument, and then try running the command again.
    At line:1 char:118
    + … er *).Name | Foreach-Object {Invoke-Command –ComputerName $_ {msg * …
    + ~~
    + CategoryInfo : InvalidData: (:) [Invoke-Command], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand

    please help!

    Like

      • Hi, Now i got this error

        Invoke-Command : Cannot validate argument on parameter ‘ComputerName’. The argument is null or empty. Provide an
        argument that is not null or empty, and then try the command again.
        At line:1 char:117
        + … ter *).Name | Foreach-Object {Invoke-Command -ComputerName $_ {msg * …
        + ~~
        + CategoryInfo : InvalidData: (:) [Invoke-Command], ParameterBindingValidationException
        + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.InvokeCommandCommand

        Any idea?

        Like

Leave a Reply to Michael Perry Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.