This worklet uses msg.exe to send a notification to the currently logged in user. Once the user dismisses the notification message, a reboot command is immediately issued.
Things you can change in the script:
- Edit the “Message to end user” to put in your custom message, such as “Click OK to reboot now”
- The time to wait for the scheduled task. In this case we used 30 seconds but you might be able to get away with a shorter time window. In any case, this doesn’t matter to the end user, just to how quickly the task runs after the policy executes.
- The reboot command - you can make this anything you want, such as a noisy installer that you can’t get to install silently, or an install that will interrupt the user’s work.
There’s a couple of tricks that make this worklet function. First is the use of the Task Scheduler to run msg.exe as the currently logged in user (thanks to @rich for showing me this trick) . This does two things:
- allows msg.exe to run (it has permissions issues when you try to run it as System, which is what worklets run as)
- uses the /W flag on the msg.exe command which stops the executable from finishing. Technically the /W is designed to make the msg.exe wait for a return message, but in this case we just wanting the executable to wait until the msg window is dismissed by the user
To make the Powershell script wait for the msg.exe command to finish before jumping to the reboot command, we pipe the msg.exe output to Out-Null. By default, Powershell doesn’t wait for a command to finish before jumping to the next line in the script.
Things to know:
- This won’t work on Windows 10 Home edition, as it does not include the msg.exe function for some reason. Attempts to copy msg.exe over from another machine don’t seem to work.
- The command
$currentusr = (Get-WmiObject -class win32_process -ComputerName 'localhost' | Where-Object name -Match explorer).getowner().userwill return all currently logged in users, which means this script will fail on a machine where more than one person is logged in at a time and just run the reboot command without sending a msg.exe message. If you need it to work in these conditions then you can parse out just the first user from the returned list, but then only one user will get notified and control the timing of the reboot. The second and subsequent users will just get a mystery reboot.
- This worklet saves a temporary .ps1 file in the c:\ProgramData\Amagent folder, for the Task Scheduler to run, but you can use another location for that if you prefer.
- The originator of the msg.exe command is the currently logged in user, which makes it look like the user is sending themselves a message (might need to educate users if they find this confusing).
- This should work on earlier versions of Windows, but I’ve only tested it with Windows 10 (admin or standard user both work fine as the currently logged in user).
Evaluation code: (returns exit code of 1 so that the policy will always run the remediation code when scheduled)
$execute = "C:\Windows\System32\msg.exe" $message = "Message to end user" $time = (Get-Date).AddSeconds(30) $triggerAt = New-ScheduledTaskTrigger -At $time -Once $currentusr = (Get-WmiObject -class win32_process -ComputerName 'localhost' | Where-Object name -Match explorer).getowner().user $argument = $currentusr + " " + "/W" + " " + $message $command = $execute + " " + $argument + " " + "| Out-Null `n" New-Item -Path "c:\ProgramData\Amagent" -Name "message.ps1" -ItemType "file" -Value $command Add-Content -Path "c:\ProgramData\Amagent\message.ps1" -Value "Restart-Computer" $action = New-ScheduledTaskAction -Execute Powershell.exe -Argument "-windowstyle hidden c:\ProgramData\Amagent\message.ps1" Register-ScheduledTask -TaskName "StartMsg" -Trigger $triggerAt -Action $action -User $currentusr Start-Sleep 31 Unregister-ScheduledTask -TaskName "StartMsg" -Confirm:$false Remove-Item -Path "c:\ProgramData\Amagent\message.ps1"