Powershell:当 运行 作为本地系统时,向登录用户发送 toast 通知

Powershell: Send a toast notification to logged user when running as Local System

我有一个脚本 运行 作为本地系统,它会执行一些操作,包括检查是否有用户登录,如果是,它会运行一个 PowerShell 片段来显示 toast 通知,如下所示。

如果 PS 以当前用户身份运行,它工作正常。如果它作为 LocalSystem 运行,则当前用户看不到 toast,因为输出是 Session 0(对于本地系统帐户)。

如果 运行 作为本地系统并且 没有 请求用户凭据,是否可以向登录用户显示 toast 通知?

Add-Type -AssemblyName System.Windows.Forms 
$global:balloon = New-Object System.Windows.Forms.NotifyIcon
$path = (Get-Process -id $pid).Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) 
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info 
$balloon.BalloonTipText = "$Text"
$balloon.BalloonTipTitle = "$Title" 
$balloon.Visible = $true 
$balloon.ShowBalloonTip($Miliseconds)

简介

如果 运行在 Session 0 中以 SYSTEM 用户身份使用 PowerShell 可以向当前活动的登录用户显示 toast 通知而不请求用户的凭据。

执行此操作的两个解决方案遵循 "Background Notes"

背景说明

请注意,本节是为所有 post 的观众而写的,而不仅仅是原来的提问者。

SYSTEM指的是同义词NT Authority\SYSTEMLocal System

许多 Windows 服务 运行 作为 SYSTEM 用户,尽管其他 运行 作为具有较少权限的用户,例如 LOCAL SERVICENETWORK SERVICE .

为每个登录用户创建一个 Windows 会话,从 1 开始编号,其中包含用户的 windows。

还创建了一个名为 Session 0 的额外后台会话,Windows 服务和用户模式驱动程序 运行 位于其中。更多信息请见以下 link.

Sessions, Desktops, and Windows Stations

每用户服务 运行 之外的所有服务 会话 0

如果您正在为此类脚本使用服务,我建议您考虑使用以下两种选择之一:

  1. 创建一个 Session 1 子流程,正如一些服务已经完成的那样。

  2. 使用 Windows Task Scheduler 代替 运行 您在与当前活动用户相同的会话中的主脚本或通知脚本。此计划任务可以设置为触发事件。

请注意以下安全警告。 Powershell 5 中的脚本可以被中断,从而将控制权交给用户。对于 Powershell 6 及更高版本,可使用非交互选项禁用此行为。

解决方案注释

下面给出两种解决方案来解决原来的问题。两者都使用中间程序从 Session 0 移动到 Session 1.

两者都会短暂闪烁一个 PowerShell window,这让用户感到不安并且很难隐藏。下面的 link 中提供了一些隐藏技巧。

How To Run A PowerShell Script Without Display a Window

输入的以下解决方案需要不带空格的路径。如果给定,必须使用完整路径。您将必须编辑这些路径以适应。

包括测试方法。

第一个解决方案需要 PSExec.exe 程序。它是 PSTools 的一部分,可在以下 link 中找到。它还用于测试这两种解决方案。

PSTools

第二种解决方案需要 ServiceUI.exe 程序。它是以下 link.

可用的 Microsoft Deployment Toolkit (MDT) 的一部分

Microsoft Deployment Toolkit (MDT)

ServiceUI.exe程序埋在MDT安装目录如下。

Microsoft Deployment Toolkit\Templates\Distribution\Tools\x64\ServiceUI.exe

我将其复制到 E:\Programs\MDT\ServiceUI.exe 以使其在 PowerShell 中更易于使用

需要脚本

气球测试。ps1

$Miliseconds=50000
$Text="Hi"
$Title="Test"

Add-Type -AssemblyName System.Windows.Forms 
$global:balloon = New-Object System.Windows.Forms.NotifyIcon
$path = (Get-Process -id $pid).Path
$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) 
$balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info 
$balloon.BalloonTipText = "$Text"
$balloon.BalloonTipTitle = "$Title" 
$balloon.Visible = $true 
$balloon.ShowBalloonTip($Miliseconds)

WhoAmISession.ps1

whoami
$Session=(Get-Process -PID $pid).SessionID
echo "Session=$Session"

测试设置

开始 cmd.exe window 作为 Administrator.

以下命令将提供SYSTEM用户Session 0 PowerShell执行环境,如图所示

E:\Programs\PSTools\psexec -s powershell.exe -file e:\test\WhoAmISession.ps1

解决方案 1:PSExec.exe

如果运行宁作为 Session 0 中的 SYSTEM 用户,要在 PowerShell 中显示 Toast 通知,请使用以下命令。如果当前活动登录用户正在使用 Session 1,这将显示 Toast 通知。其他会话需要进行一些修改。

E:\Programs\PSTools\psexec -s -i 1 C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -file e:\test\BalloonTest.ps1

要测试启动 "cmd.exe" window 作为 "Administrator" 并输入以下命令。如镜像所显示,如果当前活动登录用户使用 Session 1,这将显示 Toast 通知。

E:\Programs\PSTools\psexec -s powershell.exe E:\Programs\PSTools\psexec -s -i 1 C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -file e:\test\BalloonTest.ps1

解决方案 2:ServiceUI.exe

如果运行宁作为 Session 0 中的 SYSTEM 用户,要在 PowerShell 中显示 Toast 通知,请使用以下命令。这将向当前活动的登录用户显示 Toast 通知。

E:\Programs\MDK\ServiceUI.exe C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -file e:\test\BalloonTest.ps1

要测试启动 "cmd.exe" window 作为 "Administrator" 并输入以下命令。如镜像所显示,这将向当前活动登录的用户显示 Toast 通知。

E:\Programs\PSTools\psexec -s powershell.exe E:\Programs\MDK\ServiceUI.exe C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -file e:\test\BalloonTest.ps1

展开就很好:

作为另一个用户运行的替代解决方案

当从 SYSTEM:

调用时,以下脚本可以以类似的方式用作上述答案中提到的 psexecServiceUI.exe 的替代方案

请注意,我还没有对这些选项进行广泛测试,并且可能存在限制,例如wrt 执行用户(如果不是 SYSTEM)或 execution policies,具体取决于您在何种上下文中使用它们。 尽管如此,我认为它们值得一提,因为它们可以成为某些用例的良好解决方案。

使用计划任务

提到了使用计划任务的可能性,但没有展开。

此解决方案具有以下优点(部分与其他解决方案共享):

  • 它不需要任何外部工具或脚本installation/import
  • 它可以是 SYSTEM 之外的 运行(例如,由具有管理权限的不同用户)
  • 可以自动化(不需要用户手动注册一个专门的任务)
  • 它可以被原子化(只有toast-displaying代码可以推迟到一个专门的任务,把主脚本留给运行根据自己的条件单独处理)。

这个想法是注册一个将执行 PowerShell 的计划任务,告诉它 运行 一个 toast-displaying 脚本。该任务已注册 运行 作为当前 logged-in 用户,由 script-running 而非 logged-in 用户手动立即触发 - 例如 SYSTEM), 最后删除.

这是实现此目的的基本代码,假设 toast 显示逻辑包含在 toast-showing-script.ps1 文件1 中,例如,需要一个 ToastScriptParam参数:

$LoggedInUser = Get-CimInstance –ClassName Win32_ComputerSystem | Select-Object -expand UserName

$TaskAction = New-ScheduledTaskAction -Execute “powershell.exe” -Argument "-NoLogo -NonInteractive -WindowStyle Hidden -ExecutionPolicy RemoteSigned -File .\path\to\toast-showing-script.ps1 -ToastScriptParam ""$ToastContent"""  # clearly, you will want to adapt the arguments to fit your use case
$TaskPrincipal = New-ScheduledTaskPrincipal -UserId $LoggedInUser
$Task = New-ScheduledTask -Action $TaskAction -Principal $TaskPrincipal

$ScheduledTask = $null
try {
    $ScheduledTask = Register-ScheduledTask -TaskName 'TempToast' -TaskPath '\TempToast' -InputObject $Task
    Start-ScheduledTask -InputObject $ScheduledTask
} finally {
    if ($ScheduledTask) {
        Unregister-ScheduledTask -InputObject $ScheduledTask -Confirm:$false
    }
}

作为进一步的例子,您可以参考(或使用)这个 ToastNotification PS module 我写的就是这样做的,但提供了更多的选择。它允许任何用户简单地调用 Show-NotificationToLoggedInUser -Title "A title" -Message "Longer message" 来向当前登录的用户显示 toast 通知。它是另一个不相关项目的一部分,但模块本身可以用作 stand-alone.


1 当然,您也可以将此逻辑放在脚本块中。请记住,在计划任务的情况下,powershell.exe 不是“来自另一个 PowerShell 主机的 运行”,因此您不能将 ScriptBlock 传递给它,只能传递一个字符串。详见Microsoft documentation on the -Command option