IWA
2025-10-24
点 赞
0
热 度
17
评 论
0

💻 用 PowerShell + ps2exe 打造 Windows 托盘护眼提醒助手(支持自定义图标、自启、锁屏)

👀 长时间盯着屏幕眼睛酸痛?用几行 PowerShell 脚本就能打造一个轻量的 Windows 托盘护眼助手
支持定时提醒、系统托盘运行、自定义图标、自动锁屏、以及开机自启。


✨ 功能简介

本教程实现一个轻量的 护眼提醒工具 EyeRest

  • 🕒 每隔 N 分钟提醒你休息眼睛(自定义时间)

  • 💬 桌面右下角弹出提示气泡

  • 🔒 提示后自动锁屏(可选)

  • 🧠 托盘图标运行,不占用任务栏

  • ⚙️ 右键托盘菜单可:

    • 修改提醒时间

    • 手动退出

    • 打开/关闭「开机自启」

  • 🖼️ 可自定义托盘图标(ICO)

  • 🚀 打包成单个 .exe,方便分享给电脑小白使用


🧩 一、准备工作

1. 所需文件

  • 一个 .ps1 脚本(主程序)

  • 一个 .ico 图标文件(自定义托盘图标)

文件结构如下:

EyeRest/
 ├─ EyeRest.ps1     # 主脚本(你将要编写)
 └─ eye.ico         # 托盘图标

2. 安装 ps2exe 打包工具

打开 PowerShell(以管理员身份)运行:

Install-Module -Name ps2exe -Force

💡 二、脚本核心逻辑

脚本使用以下几个 .NET 命名空间:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
  • System.Windows.Forms.NotifyIcon:实现托盘图标

  • System.Timers.Timer:实现定时提醒

  • System.Windows.Forms.ContextMenuStrip:右键菜单

  • rundll32.exe user32.dll,LockWorkStation:锁屏命令


📜 三、完整源码

💾 将以下内容保存为 EyeRest.ps1


👉 源码开始:

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# ICO Base64 字符串
$iconBase64 = @"
<BASE64_STRING>
"@  # 省略中间内容,完整 Base64 写入

# 从 Base64 创建图标对象
$iconBytes = [System.Convert]::FromBase64String($iconBase64)
$ms = New-Object System.IO.MemoryStream(,$iconBytes)
$icon = New-Object System.Drawing.Icon($ms)

# 托盘图标
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
$notifyIcon.Icon = $icon
$notifyIcon.Text = "护眼提醒-IWA"
$notifyIcon.Visible = $true

# ============================
# 默认提醒间隔(分钟)
$intervalMinutes = 30

# 是否启用自动锁屏
$global:autoLockEnabled = $true

# 创建右键菜单
$menu = New-Object System.Windows.Forms.ContextMenuStrip

# 设置提醒间隔菜单
$setIntervalItem = $menu.Items.Add("设置提醒间隔")
$setIntervalItem.Add_Click({
    # 创建自定义输入框
    $form = New-Object System.Windows.Forms.Form
    $form.Text = "设置提醒间隔(分钟)"
    $form.Size = New-Object System.Drawing.Size(300,150)
    $form.StartPosition = "CenterScreen"
    $form.FormBorderStyle = "FixedDialog"
    $form.MaximizeBox = $false
    $form.MinimizeBox = $false

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

    $okButton = New-Object System.Windows.Forms.Button
    $okButton.Text = "确定"
    $okButton.Location = New-Object System.Drawing.Point(60,60)
    $okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })
    $form.Controls.Add($okButton)

    $cancelButton = New-Object System.Windows.Forms.Button
    $cancelButton.Text = "取消"
    $cancelButton.Location = New-Object System.Drawing.Point(150,60)
    $cancelButton.Add_Click({ $form.Tag = $null; $form.Close() })
    $form.Controls.Add($cancelButton)

    $form.Topmost = $true
    $form.ShowDialog() | Out-Null

    if ($form.Tag -ne $null) {
        if ([int]::TryParse($form.Tag,[ref]$null) -and [int]$form.Tag -gt 0) {
            $global:intervalMinutes = [int]$form.Tag
            $timer.Interval = $intervalMinutes * 60 * 1000
            [System.Windows.Forms.MessageBox]::Show("提醒间隔已设置为 $intervalMinutes 分钟","提示",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information)
        } else {
            [System.Windows.Forms.MessageBox]::Show("请输入有效的整数分钟数。","错误",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Error)
        }
    }
})


# 自动锁屏开关菜单
$autoLockItem = $menu.Items.Add("自动锁屏:开")
$autoLockItem.Add_Click({
    if ($global:autoLockEnabled) {
        $global:autoLockEnabled = $false
        $autoLockItem.Text = "自动锁屏:关"
        [System.Windows.Forms.MessageBox]::Show("已关闭自动锁屏功能","提示",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information)
    } else {
        $global:autoLockEnabled = $true
        $autoLockItem.Text = "自动锁屏:开"
        [System.Windows.Forms.MessageBox]::Show("已开启自动锁屏功能","提示",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information)
    }
})

# ==========================
# 开机自启菜单逻辑(修正版)
# ==========================

$regKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"
$regValueName = "EyeRest"

# 获取当前程序路径(支持 exe / ps1)
try {
    $exePath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
} catch {
    if ($MyInvocation.MyCommand.Path) {
        $exePath = $MyInvocation.MyCommand.Path
    } else {
        $exePath = (Join-Path $PSScriptRoot "EyeRest.exe")
    }
}

function Get-AutoStartStatus {
    try {
        $value = (Get-ItemProperty -Path $regKey -Name $regValueName -ErrorAction SilentlyContinue).$regValueName
        return ($value -eq $exePath)
    } catch {
        return $false
    }
}

function Set-AutoStart($enable) {
    if ($enable) {
        Set-ItemProperty -Path $regKey -Name $regValueName -Value $exePath
    } else {
        Remove-ItemProperty -Path $regKey -Name $regValueName -ErrorAction SilentlyContinue
    }
}

# 初始化菜单项文字
$isAutoStart = Get-AutoStartStatus
$autoStartText = if ($isAutoStart) { "开机自启:开" } else { "开机自启:关" }
$autoStartItem = $menu.Items.Add($autoStartText)

# 点击事件
$autoStartItem.Add_Click({
    $current = Get-AutoStartStatus
    if ($current) {
        Set-AutoStart $false
        $autoStartItem.Text = "开机自启:关"
        [System.Windows.Forms.MessageBox]::Show("已关闭开机自启","提示",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information)
    } else {
        Set-AutoStart $true
        $autoStartItem.Text = "开机自启:开"
        [System.Windows.Forms.MessageBox]::Show("已开启开机自启","提示",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information)
    }
})





# 退出菜单
$exitItem = $menu.Items.Add("退出")
$exitItem.Add_Click({
    $notifyIcon.Visible = $false
    [System.Windows.Forms.Application]::Exit()
})

$notifyIcon.ContextMenuStrip = $menu

# 提醒函数
function Show-Reminder {
    $notifyIcon.BalloonTipTitle = "IWA的艺术编程,休息一下 👀"
    $notifyIcon.BalloonTipText = "$intervalMinutes 分钟到了,离开屏幕看看远处!"
	if ($global:autoLockEnabled) {
        $notifyIcon.BalloonTipText += "`n10 秒后将自动锁屏。"
    }
    $notifyIcon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Info
    $notifyIcon.ShowBalloonTip(5000)
	# 延迟 10 秒后锁屏
    if ($global:autoLockEnabled) {
        Start-Job -ScriptBlock {
            Start-Sleep -Seconds 10
            rundll32.exe user32.dll,LockWorkStation
        } | Out-Null
    }
}

# 定时器
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = $intervalMinutes * 60 * 1000
$timer.Add_Tick({ Show-Reminder })
$timer.Start()

# 启动时先提醒一次
Show-Reminder

# 保持托盘应用运行
[System.Windows.Forms.Application]::Run()

#  $iconBytes = [System.IO.File]::ReadAllBytes("Desktop/iwa.ico")
#  [System.IO.File]::WriteAllText("eye.txt", [System.Convert]::ToBase64String($iconBytes))
#  ps2exe -inputFile "C:\Users\my\Desktop\EyeRest-IWA.ps1" -outputFile "C:\Users\my\Desktop\护眼提醒-IWA.exe" -noConsole -iconFile "C:\Users\my\Desktop\iwa.ico"

👈 源码结束


🧰 四、打包成 EXE

在 PowerShell 中运行以下命令:

ps2exe -inputFile "C:\Users\my\Desktop\EyeRest\EyeRest.ps1" `
       -outputFile "C:\Users\my\Desktop\EyeRest\EyeRest.exe" `
       -noConsole `
       -iconFile "C:\Users\my\Desktop\EyeRest\eye.ico"

参数说明:

参数

说明

-inputFile

原始 .ps1 脚本路径

-outputFile

生成的 .exe 文件路径

-noConsole

运行时不显示控制台窗口

-iconFile

托盘图标(ICO 文件)


🔧 五、运行效果

  1. 双击运行 EyeRest.exe

  2. 系统托盘出现你的自定义图标

  3. 每隔 N 分钟弹出休息提醒

  4. 提醒后 10 秒自动锁屏

  5. 右键图标菜单可修改时间或退出


🔄 六、开机自启说明

启用“开机自启”后,脚本会在注册表路径:

HKCU\Software\Microsoft\Windows\CurrentVersion\Run

自动创建一条记录:

名称:EyeRest
数据:"C:\Users\my\Desktop\EyeRest\EyeRest.exe"

再次点击即可关闭自启。


⚠️ 七、常见问题

问题

原因

解决方法

提示“无法将参数绑定到 Path”

$exePath 为空

使用 [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName

自定义图标不显示

.ico 文件未放在脚本同目录

放在同目录下或指定绝对路径

打包后弹出黑色窗口

忘记添加 -noConsole 参数

重新打包添加该参数


🎁 八、效果图


🧠 九、总结

这个小工具的关键点在于:

  • PowerShell + .NET 能轻松调用系统组件;

  • ps2exe 可将脚本封装成单个可执行文件;

  • 利用注册表管理自启;

  • 全程无第三方依赖,轻量可靠。


📦 十、下载与分享

我已经将打包好的 EyeRest.exe 上传到附件中,你也可以直接下载!

护眼提醒-IWA.exe


🧑‍💻 作者:Rock
📅 更新日期:2025-10-23
💬 如果这篇教程对你有帮助,欢迎点赞 / 收藏 / 分享


🧩 解决托盘图标不随 EXE 打包的问题:使用 Base64 内嵌图标

因为 ps2exe 在打包时不会自动把外部 .ico 文件一起打包进可执行文件中,所以当别人运行时,图标路径就会失效。
最好的做法是:把图标文件转成 Base64 字符串,写入脚本内部,运行时自动解码生成临时图标。

第一步:把 .ico 转成 Base64 字符串

在 PowerShell 中执行以下命令:

$iconBytes = [System.IO.File]::ReadAllBytes("C:\Users\my\Desktop\eye.ico")
[System.IO.File]::WriteAllText("C:\Users\my\Desktop\eye_base64.txt", [System.Convert]::ToBase64String($iconBytes))

执行后会生成一个文件:

C:\Users\my\Desktop\eye_base64.txt

里面是一长串 Base64 编码文本。

第二步:在脚本中加载这个 Base64 图标

在你的主脚本中(例如 EyeRest.ps1),添加以下代码片段到顶部区域(初始化托盘图标之前):

# ==== 图标加载(Base64 内嵌) ====
# 把下面的 <BASE64_STRING> 替换成你生成的 eye_base64.txt 内容
$base64Icon = @"
<BASE64_STRING>
"@

# 运行时将 Base64 转换为临时图标文件
$tempIconPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "eye.ico")
[System.IO.File]::WriteAllBytes($tempIconPath, [System.Convert]::FromBase64String($base64Icon))
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($tempIconPath)

✅ 注意:打包前记得替换 <BASE64_STRING> 为你自己的内容。

第三步:重新打包

然后重新打包:

ps2exe -inputFile "C:\Users\my\Desktop\EyeRest.ps1" -outputFile "C:\Users\my\Desktop\EyeRest.exe" -noConsole

现在生成的 EXE 不再依赖外部图标文件



用键盘敲击出的不只是字符,更是一段段生活的剪影、一个个心底的梦想。希望我的文字能像一束光,在您阅读的瞬间,照亮某个角落,带来一丝温暖与共鸣。

IWA

estp 企业家

具有版权性

请您在转载、复制时注明本文 作者、链接及内容来源信息。 若涉及转载第三方内容,还需一同注明。

具有时效性

文章目录

IWA的艺术编程,为您导航全站动态

30 文章数
9 分类数
10 评论数
30标签数
最近评论
IWA

IWA


👍

M丶Rock

M丶Rock


😂

M丶Rock

M丶Rock


感慨了

M丶Rock

M丶Rock


厉害了

M丶Rock

M丶Rock


6666666666666666666