怎么用脚本获取软件安装日期

wen 实用脚本 1

脚本实现方法与实战指南

目录导读

  1. 为什么需要获取软件安装日期? – 场景与价值
  2. 核心原理:软件安装信息的存储机制 – Windows注册表与日志文件
  3. 用PowerShell脚本自动化查询 – 实战代码与参数详解
  4. 用Python跨平台脚本实现 – 兼容macOS与Linux
  5. 批量扫描与导出报告 – 企业级应用方案
  6. 常见问题与避坑指南 – Q&A解答
  7. 延伸:脚本安全性与权限管理 – 避免误操作

为什么需要获取软件安装日期?

在日常运维、系统审计或软件合规管理中,获得每款软件的精确安装日期往往比版本号更关键。

怎么用脚本获取软件安装日期

  • 补丁回溯:某软件在浏览器漏洞爆发前安装,需确认时间线
  • 许可证管理:试用版软件若超期,需立即停用
  • 事故分析:当恶意软件植入时,安装日期是追查线索

传统方法(如手动查看“控制面板-程序和功能”)效率低且无法批量处理,而脚本能通过系统底层接口,秒级获取上百款软件的安装时间。


核心原理:软件安装信息的存储机制

在不同操作系统中,软件安装日期记录方式各异:

Windows系统

  • 注册表路径HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
    每个子项对应一个已安装软件,其中InstallDate键值存储日期(格式为yyyyMMdd)。
    注意:部分旧软件可能缺失此键值,需改用文件创建时间(例如.exe安装目录的创建时间)。

  • 日志文件C:\Windows\Inf\setupapi.dev.log和系统事件日志(Event Log)中记录安装行为。

macOS系统

  • 收据(Receipt)数据库/Library/Receipts/private/var/db/receipts/
    每个.plist文件包含InstallDate字段。

Linux系统(Debian/Ubuntu为例)

  • dpkg日志/var/log/dpkg.log按时间顺序记录所有软件包操作。
    使用grep " install " /var/log/dpkg.log | grep software-name可过滤特定版本。

核心逻辑:脚本本质上是通过读取操作系统的“已安装软件清单”并提取日期字段,若遇上缺失,则回退到程序文件本身的元数据(如创建时间)。


用PowerShell脚本自动化查询(Windows最推荐)

PowerShell是Windows原生脚本环境,无需安装额外工具。

基础脚本(获取所有软件安装日期)

# 获取所有Uninstall子项,并提取InstallDate
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
$software = Get-ItemProperty -Path $key | Where-Object {$_.DisplayName -ne $null}
$result = foreach ($item in $software) {
    [PSCustomObject]@{
        Name        = $item.DisplayName
        Version     = $item.DisplayVersion
        InstallDate = $item.InstallDate
        InstallTimestamp = if ($item.InstallDate) { 
            [datetime]::ParseExact($item.InstallDate, "yyyyMMdd", $null)
        } else { "未知" }
    }
}
# 按日期倒序输出
$result | Sort-Object InstallDate -Descending | Format-Table -AutoSize

增强脚本(处理缺失日期+导出CSV)

$results = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | 
    ForEach-Object {
        $name = $_.GetValue("DisplayName")
        if ($name) {
            # 尝试从注册表获取日期
            $regDate = $_.GetValue("InstallDate")
            # 如果注册表无日期,获取主执行文件的创建时间(需要32位偏移)
            $installLocation = $_.GetValue("InstallLocation")
            if (!$regDate -and $installLocation) {
                $exeFiles = Get-ChildItem $installLocation -Filter "*.exe" -Recurse -ErrorAction SilentlyContinue |
                    Sort-Object CreationTime -Descending | Select-Object -First 1
                $regDate = if ($exeFiles) { $exeFiles.CreationTime.ToString("yyyyMMdd") } else { $null }
            }
            [PSCustomObject]@{
                SoftwareName = $name
                InstallDate  = if ($regDate) { 
                    try { [datetime]::ParseExact($regDate, "yyyyMMdd", $null).ToString("yyyy-MM-dd") }
                    catch { $regDate }
                } else { "无法获取" }
            }
        }
    }
# 导出CSV报告
$results | Export-Csv -Path "SoftwareInstallDates.csv" -NoTypeInformation -Encoding UTF8
Write-Host "报告已生成:SoftwareInstallDates.csv"

执行提示:以管理员身份运行PowerShell,确保能访问所有注册表项,若系统是32位软件安装在64位系统,还需同时查询HKLM:\SOFTWARE\WOW6432Node\...路径。


用Python跨平台脚本实现

Python凭借winreg(Windows)和os模块,可轻松覆盖多系统。

Windows版本(使用winreg模块)

import winreg
from datetime import datetime
def get_windows_software_install_dates():
    key_path = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    # 同时查询32位兼容路径
    paths = [key_path, r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"]
    results = []
    for base_path in paths:
        try:
            key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, base_path)
            for i in range(0, winreg.QueryInfoKey(key)[0]):
                subkey_name = winreg.EnumKey(key, i)
                subkey = winreg.OpenKey(key, subkey_name)
                try:
                    name = winreg.QueryValueEx(subkey, "DisplayName")[0]
                    install_date_str = winreg.QueryValueEx(subkey, "InstallDate")[0]
                    install_date = datetime.strptime(install_date_str, "%Y%m%d").strftime("%Y-%m-%d")
                    results.append({"name": name, "date": install_date})
                except FileNotFoundError:
                    continue
        except PermissionError:
            continue
    # 按日期排序(最近安装在前)
    results.sort(key=lambda x: x['date'], reverse=True)
    return results
if __name__ == "__main__":
    for sw in get_windows_software_install_dates()[:20]:  # 显示前20条
        print(f"{sw['date']} - {sw['name']}")

macOS/Linux版本(解析dpkg日志与收据文件)

import os, re, subprocess, plistlib
from datetime import datetime
def get_linux_install_dates():
    # 解析dpkg日志(Ubuntu/Debian)
    log_path = "/var/log/dpkg.log"
    dates = {}
    with open(log_path, 'r') as f:
        for line in f:
            match = re.search(r'^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) install (.+?) ', line)
            if match:
                date_str = match.group(1)
                pkg_name = match.group(2)
                # 保留最早安装日期(排除重复行)
                if pkg_name not in dates:
                    dates[pkg_name] = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
    return dates
def get_macos_install_dates():
    receipts_path = "/Library/Receipts/"
    dates = {}
    for root, dirs, files in os.walk(receipts_path):
        for file in files:
            if file.endswith(".plist"):
                full_path = os.path.join(root, file)
                try:
                    with open(full_path, 'rb') as pf:
                        plist = plistlib.load(pf)
                        name = plist.get('PackageIdentifier') or plist.get('CFBundleIdentifier')
                        install_date = plist.get('InstallDate')
                        if name and install_date:
                            dates[name] = install_date
                except:
                    pass
    return dates

注意:Linux脚本需sudo权限读取dpkg.log;macOS部分收据文件需root权限访问(如Apple系统更新)。


批量扫描与导出报告(企业级应用)

企业环境需结合AD域或SCCM(System Center Configuration Manager)进行远程采集,以下为纯脚本的进阶方案:

# 远程多台计算机查询(需WinRM启用)
$computers = @("Server01", "PC-Office-22", "192.168.1.100")
$cred = Get-Credential
Invoke-Command -ComputerName $computers -Credential $cred -ScriptBlock {
    param($computerName)
    # 使用第3节的函数
    $data = Get-LocalSoftwareInstallDates  # 假设已定义函数
    $data | Select-Object *, @{N="Computer";E={$env:COMPUTERNAME}}
} | Export-Csv -Path "AllSoftwareDates.csv" -NoTypeInformation

场景示例:企业发现某安全软件必须于3天内全部升级,通过脚本扫描所有PC的安装日期,生成已安装旧版本的机器清单,甚至能倒算哪些设备在试用到期前必须处理。


常见问题与避坑指南(Q&A)

Q1:为什么某些软件显示“未知”安装日期?

:原因有三:

  • 软件安装时未向注册表写入InstallDate键值(常见于绿色版或旧程序)。
  • 系统清理工具(如CCleaner)删除了相关记录。
  • 软件是系统组件(如.NET Framework)或通过DISM/AppX安装,其日期存储在不同位置。
    解决:回退法——检查安装目录下的主程序(如notepad.exe)的CreationTime,或搜索setup.log文件。

Q2:PowerShell脚本执行报错“拒绝访问”?

:注册表路径HKLM需要管理员权限,请以管理员身份打开PowerShell(右键→以管理员身份运行),若使用32位PowerShell(x86)访问64位注册表,需额外指定-Reg 或使用Get-ChildItem -Path HKLM:\SOFTWARE\WOW6432Node\...

Q3:脚本返回的InstallDate格式不一致(如“20230815” vs “2023/08/15”)?

:依据原始存储格式统一转换,若遇到非标准字符串(如2023-08-15),可在PowerShell中使用[datetime]::TryParse()或Python的dateutil库容错。

Q4:如何在macOS上获取App Store下载的软件日期?

:App Store应用安装记录存储在~/Library/Receipts/,但部分软件(如Xcode)也可能在系统Receipts目录中,运行第4节的macOS函数可覆盖大部分情况,对于通过codesign签名的软件,还可以用pkgutil --pkg-info <bundle-id>命令查询。

Q5:脚本会修改系统数据么?

:本文所有脚本仅执行只读操作(读取注册表、文件属性、解析日志),不会增加、删除或修改任何系统文件,放心使用,但建议初次在测试环境验证。


延伸:脚本安全性与权限管理

  • 最小权限原则:日常查询建议使用普通账户+只读命令(如Python读取用户级注册表HKEY_CURRENT_USER下的安装信息),避免频繁提权。
  • 日志脱敏:导出CSV报告时,若包含员工姓名或IP,建议提前过滤或加密(如使用ConvertTo-SecureString)。
  • 远程执行风险:Invoke-Command脚本块若被拦截可执行恶意代码,应仅在受信任网络上使用,并绑定SSL证书(WinRM HTTPS)。

选择适合你的脚本方案

场景 推荐方案 核心优势
单机快速查询 PowerShell(第3节) 无需环境配置,支持所有Win版本
跨平台/二次开发 Python(第4节) 代码可移植,易整合至运维平台
企业批量审计 PowerShell+远程执行(第5节) 支持域环境,输出标准CSV
无人值守定时扫描 脚本+任务计划程序 自动归档历史安装记录

最后提醒:软件安装日期仅反映“何时安装”,而非“何时首次运行”,对于持续更新的软件(如浏览器),建议结合版本号判断是否为最新,脚本的世界里,精确的时间线才是自动化运维的基石。

抱歉,评论功能暂时关闭!