如何在PowerShell中获取所有已打开Excel工作簿列表(含只读文件)
问题原因分析
- 最初使用
Get-Process只能拿到单个Excel窗口标题的原因:Excel默认采用单进程多文档界面设计,进程的MainWindowTitle属性仅返回当前处于激活状态的顶层窗口标题,其余打开的工作簿窗口不会被该属性捕获。 - 后续使用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
相关产品推荐
相关产品推荐

