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!"
* 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:
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"}}
Client01 is a member of the OU Workstation. Client01 receives the message.
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
Categories: Cyber Security, PowerShell, Windows Server
I got our message to work over our domain but (and this may seem like a simple thing) but how do we get a sound to play when the message hits our computers. I have a sound associated with Instant Message Notification but nothing plays.
LikeLike
Hi,
it is not working. I am domain admin and local admin and starting cmd/ps with admin rights but still i got error
PS C:\Windows\system32> msg * “Hallo, this is a test!”
Error sending message to session Console : Error 2147500058
Error [2147500058]:The server process could not be started because the configured identity is incorrect. Check the username and password.
LikeLike
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?
LikeLike
Hi, use Read-Host instead of Write-Host.
LikeLike
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
LikeLike
Thank you! A nice demonstration of the Parallel feature in PowerShell 7. great one !
LikeLike
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.
LikeLike
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
LikeLike
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}}
LikeLike
You could try putting the Get-ADcomputer command inside the brackets of the If condition. There is something wrong with your $x variable.
LikeLike
(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.
LikeLike
You’re welcome.
LikeLike
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!
LikeLike
In order to use this commands the AD module must be installed.
LikeLike
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.
LikeLike
Thank you for your feedback. The script is designed mainly for small and middle environments.
LikeLike
Thank you very much for your Reply, and i v learnt so much here, my other Question is, what about if am running on maybe 2000 Computer accounts, is there an alternative to this Script? putting into consideration the Prerequisites ?
LikeLike
I guess SCCM can do this. Just research there are tools that uses a client software installed on your cluents
LikeLike
Thanks so much, let me research on that. God Bless you
LikeLiked by 1 person
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?
LikeLike
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?
LikeLike
Hi,
Have you followed the instructions of the prerequisites? WinRM is enabled by default on Windows Client operating systems.
Best,
P
LikeLike
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
LikeLike
put the computer inside the OU. This should solve the problem.
LikeLike
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!
LikeLike
Hi, double check the command on the left side of the pipe
LikeLike
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?
LikeLike