You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PowerShell中获取所有已打开Excel工作簿列表(含只读文件)

问题原因分析

  1. 最初使用Get-Process只能拿到单个Excel窗口标题的原因:Excel默认采用单进程多文档界面设计,进程的MainWindowTitle属性仅返回当前处于激活状态的顶层窗口标题,其余打开的工作簿窗口不会被该属性捕获。
  2. 后续使用COM接口无法获取只读工作簿的原因:GetActiveObject('Excel.Application')只能获取到系统中第一个启动的Excel实例对象,如果只读工作簿是在独立启动的新Excel实例中打开的,就不会被该方法捕获到。

解决方案1:枚举所有Excel顶层窗口获取标题(推荐,和任务管理器窗口列表结果一致)

直接调用Windows API枚举所有Excel窗口,不管是否只读、属于哪个实例都能捕获到,代码如下:

Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Text;
public class Win32API {
    public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool IsWindowVisible(IntPtr hWnd);
}
"@

$excelTitles = @()
$enumProc = [Win32API+EnumWindowsProc]{
    param($hWnd, $lParam)
    if ([Win32API]::IsWindowVisible($hWnd)) {
        $className = New-Object Text.StringBuilder 256
        [Win32API]::GetClassName($hWnd, $className, $className.Capacity) | Out-Null
        if ($className.ToString() -eq "XLMAIN") {
            $windowText = New-Object Text.StringBuilder 1024
            [Win32API]::GetWindowText($hWnd, $windowText, $windowText.Capacity) | Out-Null
            $title = $windowText.ToString()
            if ($title -ne "") {
                $excelTitles += $title
            }
        }
    }
    return $true
}
[Win32API]::EnumWindows($enumProc, [IntPtr]::Zero) | Out-Null

# 输出所有Excel工作簿标题
$excelTitles | ForEach-Object { Write-Host $_ }

该方案返回的结果和任务管理器里看到的窗口列表完全一致,包含带[只读]标识的工作簿。

解决方案2:枚举所有Excel COM实例获取完整工作簿属性

如果需要拿到工作簿的路径、是否只读等更多属性,可以用以下代码枚举所有运行中的Excel实例:

Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class Ole32 {
    [DllImport("ole32.dll")]
    public static extern int GetRunningObjectTable(int reserved, out UCOMIRunningObjectTable pprot);
    [DllImport("ole32.dll")]
    public static extern int CreateBindCtx(int reserved, out UCOMIBindCtx ppbc);
}
"@

$excelInstances = @()
$rot = $null
[Ole32]::GetRunningObjectTable(0, [ref]$rot) | Out-Null
if ($rot -ne $null) {
    $monikers = $null
    $rot.EnumRunning([ref]$monikers)
    $monikers.Reset()
    $moniker = [System.Runtime.InteropServices.UCOMIMoniker[]]::CreateInstance([System.Runtime.InteropServices.UCOMIMoniker], 1)
    while ($monikers.Next(1, $moniker, [ref]$null) -eq 0) {
        $bindCtx = $null
        [Ole32]::CreateBindCtx(0, [ref]$bindCtx) | Out-Null
        $displayName = $null
        $moniker[0].GetDisplayName($bindCtx, $null, [ref]$displayName)
        if ($displayName.StartsWith("!Excel.Application")) {
            $obj = $null
            $rot.GetObject($moniker[0], [ref]$obj)
            $excelInstances += $obj
        }
    }
}

# 遍历所有实例的所有工作簿
foreach ($excel in $excelInstances) {
    foreach ($wb in $excel.Workbooks) {
        Write-Host "工作簿名称:$($wb.Name),是否只读:$($wb.ReadOnly),路径:$($wb.FullName)"
    }
    # 释放COM对象避免内存泄漏
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
}

该方案可以获取到工作簿的完整属性,不管是哪个实例打开的、是否为只读模式都能正常识别。

内容的提问来源于stack exchange,提问作者LogaN4ik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.18 09:54:05