【问题标题】:Excel, save and close after runExcel,运行后保存并关闭
【发布时间】:2015-03-04 02:45:00
【问题描述】:

下面的脚本运行后如何保存?

脚本来自:Powershell Disk Usage Report

$erroractionpreference = "SilentlyContinue"
$a = New-Object -comobject Excel.Application
$a.visible = $True 

$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)

$c.Cells.Item(1,1) = "Server Name"
$c.Cells.Item(1,2) = "Drive"
$c.Cells.Item(1,3) = "Total Size (GB)"
$c.Cells.Item(1,4) = "Free Space (GB)"
$c.Cells.Item(1,5) = "Free Space (%)"

$d = $c.UsedRange
$d.Interior.ColorIndex = 19
$d.Font.ColorIndex = 11
$d.Font.Bold = $True

$intRow = 2

$colComputers = get-content "c:\servers.txt"
foreach ($strComputer in $colComputers)
{
$colDisks = get-wmiobject Win32_LogicalDisk -computername $strComputer -Filter "DriveType = 3" 
foreach ($objdisk in $colDisks)
{
$c.Cells.Item($intRow, 1) = $strComputer.ToUpper()
$c.Cells.Item($intRow, 2) = $objDisk.DeviceID
$c.Cells.Item($intRow, 3) = "{0:N0}" -f ($objDisk.Size/1GB)
$c.Cells.Item($intRow, 4) = "{0:N0}" -f ($objDisk.FreeSpace/1GB)
$c.Cells.Item($intRow, 5) = "{0:P0}" -f ([double]$objDisk.FreeSpace/[double]$objDisk.Size)
$intRow = $intRow + 1
}
}

根据https://social.technet.microsoft.com/Forums/windowsserver/en-US/919459dc-3bce-4242-bf6b-fdf37de9ae18/powershell-will-not-save-excel-file,这将起作用,但我无法:

Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
$Excel = New-Object -comobject Excel.Application

$Excel.Visible = $true

################
$Excel.workbooks.OpenText($file,437,1,1,1,$True,$True,$False,$False,$True,$False)
$Excel.ActiveWorkbook.SaveAs($env:tmp + "\myfile.xls", $xlFixedFormat)

$Excel.Workbooks.Close()
$Excel.Quit()    

【问题讨论】:

  • 无法集成此代码?这不仅仅是将### 之后的代码包含在第二个sn-p 末尾的问题吗?根据您的情况,您只需要将 $Excel 更改为 a$

标签: excel powershell save


【解决方案1】:

这对我有用:

$workbook.Close($false)
$excel.Quit()

[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workSheet)
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)

Remove-Variable -Name excel

【讨论】:

  • 我看到了同样的行为。我们可以在某处使用 excel 将其记录为错误吗?是否有任何地方可以获取有关该错误细节的更多详细信息?我猜它与保持 com 对象活动的 .net 引用有关,但是当脚本结束并且进程关闭时,不应该自动减少任何 com-object-reference-counting 并适当地释放它吗?
  • [GC]::Collect() 调用移动[System.Runtime.Interopservices.Marshal]::ReleaseComObject()] 调用以有效加快发布速度。 [System.GC]::WaitForPendingFinalizers() 毫无意义,因为运行时并不能完全控制释放 COM 组件的实际时间。 Remove-Variable 调用不是绝对必要的(除非你想阻止之后访问变量,但你也应该删除 $worksheet)。
  • @Groostav:这个答案中唯一真正需要的调用是$excel.Quit() - 在变量之后,引用最终自动释放已经超出范围,垃圾收集最终会运行,这可能需要一段时间。只有当你想加速释放时,才需要显式释放引用和显式垃圾回收;如需更多信息,请参阅this answer
【解决方案2】:

要正确完全关闭 Excel,您还需要释放 COM 引用。在我自己的测试中发现,删除 Excel 的变量还可以确保不存在剩余的引用,这将使 Excel.exe 保持打开状态(就像您在 ISE 中进行调试一样)。

如果不执行上述操作,如果您在任务管理器中查看,您可能会看到 Excel 仍在运行...在某些情况下,有很多副本。

这与 COM 对象如何包装在“运行时可调用包装器”中有关。

这是应该使用的骨架代码:

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$workbook = $excel.Workbooks.Add()
# or $workbook = $excel.Workbooks.Open($xlsxPath)

# do work with Excel...

$workbook.SaveAs($xlsxPath)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
# no $ needed on variable name in Remove-Variable call
Remove-Variable excel

【讨论】:

    【解决方案3】:

    搞定了! - 特别感谢@Matt

    正在运行的完整脚本:

    $erroractionpreference = "SilentlyContinue"
    $a = New-Object -comobject Excel.Application
    $a.visible = $True 
    Add-Type -AssemblyName Microsoft.Office.Interop.Excel
    $xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
    
    
    $a.Visible = $true
    
    $b = $a.Workbooks.Add()
    $c = $b.Worksheets.Item(1)
    
    $c.Cells.Item(1,1) = "Server Name"
    $c.Cells.Item(1,2) = "Drive"
    $c.Cells.Item(1,3) = "Total Size (GB)"
    $c.Cells.Item(1,4) = "Free Space (GB)"
    $c.Cells.Item(1,5) = "Free Space (%)"
    
    $d = $c.UsedRange
    $d.Interior.ColorIndex = 19
    $d.Font.ColorIndex = 11
    $d.Font.Bold = $True
    
    $intRow = 2
    
    $colComputers = get-content "c:\servers.txt"
    foreach ($strComputer in $colComputers)
    {
    $colDisks = get-wmiobject Win32_LogicalDisk -computername $strComputer -Filter "DriveType = 3" 
    foreach ($objdisk in $colDisks)
    {
    $c.Cells.Item($intRow, 1) = $strComputer.ToUpper()
    $c.Cells.Item($intRow, 2) = $objDisk.DeviceID
    $c.Cells.Item($intRow, 3) = "{0:N0}" -f ($objDisk.Size/1GB)
    $c.Cells.Item($intRow, 4) = "{0:N0}" -f ($objDisk.FreeSpace/1GB)
    $c.Cells.Item($intRow, 5) = "{0:P0}" -f ([double]$objDisk.FreeSpace/[double]$objDisk.Size)
    $intRow = $intRow + 1
    }
    }
    
    $a.workbooks.OpenText($file,437,1,1,1,$True,$True,$False,$False,$True,$False)
    $a.ActiveWorkbook.SaveAs("C:\Users\Username\Desktop\myfile.xls", $xlFixedFormat)
    
    $a.Workbooks.Close()
    $a.Quit()    
    

    【讨论】:

      【解决方案4】:

      如 MSDN 文档 here 中所述,ReleaseComObject 调用仅将该 COM 对象的引用计数器减 1。如果您的脚本具有同一个 COM 对象的多个引用,它不会释放该对象。

      文档建议使用 FinalReleaseComObject 方法来完全释放 COM 对象并一劳永逸地关闭 Excel 进程。

      请务必仅在完成 ​​COM 引用后调用此方法,因为不这样做可能会导致难以调试的错误。

      【讨论】:

        【解决方案5】:

        创建 Excel 文件:

        $Excel = New-Object -ComObject Excel.Application
        $Excel.Visible = $True 
        ......
        

        关闭 Excel:

        $Excel.Close()
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
        spps -n Excel
        

        【讨论】:

          【解决方案6】:

          $excel.Quit() 没有退出并且 OneDrive 不会上传文件时,这解决了我的问题。在我的情况下,我只需要一些自动化,在工作完成后,所有 Excel 进程都被杀死是很好的。

          $excel.Quit()
          
          # Check and you will see an excel process still exists after quitting
          # Remove the excel process by piping it to stop-process
          # Warning: This Closes All Excel Processes
          
          
          Get-Process excel | Stop-Process -Force
          

          【讨论】:

          • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation 将通过展示为什么这是解决问题的好方法,并使其对有其他类似问题的未来读者更有用,从而大大提高其长期价值。请编辑您的答案以添加一些解释,包括您所做的假设。