Find then save and close all open Excel documents

后端 未结 2 1233
不思量自难忘°
不思量自难忘° 2020-12-21 11:47

I need help creating a PowerShell script that will find all open Excel documents, then first if the document is read only close the document without saving and suppress prom

2条回答
  •  北荒
    北荒 (楼主)
    2020-12-21 12:36

    @bgalea had the right idea, but the answer is very incomplete. For one thing, re-attaching to COM objects only works in the context of the user who created the object, so you'd have to create either a scheduled task that runs as that particular user or a logoff script (automatically runs in a user's context at logoff).

    Since you apparently want to run this periodically, a logoff script probably isn't an option, so you may want to use a logon script to create a respective scheduled task for each user, e.g. like this:

    schtasks /query /tn "Excel Cleanup" >nul 2>&1
    if %errorlevel% neq 0 schtasks /create /tn "Excel Cleanup" /sc DAILY /tr "wscript.exe \"C:\path\to\your.vbs\"" /f
    

    The VBScript run by these tasks might look somewhat like this:

    On Error Resume Next
    'attach to running Excel instance
    Set xl = GetObject(, "Excel.Application")
    If Err Then
      If Err.Number = 429 Then
        'Excel not running (nothing to do)
        WScript.Quit 0
      Else
        'unexpected error: log and terminate
        CreateObject("WScript.Shell").LogEvent 1, Err.Description & _
          " (0x" & Hex(Err.Number) & ")"
        WScript.Quit 1
      End If
    End If
    On Error Goto 0
    
    xl.DisplayAlerts = False  'prevent Save method from asking for confirmation
                              'about overwriting existing files (when saving new
                              'workbooks)
                              'WARNING: this might cause data loss!
    
    For Each wb In xl.Workbooks
      wb.Save
      wb.Close False
    Next
    
    xl.Quit
    Set xl = Nothing
    

    If you want a PowerShell script instead of VBScript you need to use the GetActiveObject() method to attach to a running Excel instance.

    try {
      # attach to running Excel instance
      $xl = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
    } catch {
      if ($_.Exception.HResult -eq -2146233087) {
        # Excel not running (nothing to do)
        exit 0
      } else {
        # unexpected error: log and terminate
        Write-EventLog -LogName Application `
          -Source 'Application Error' `
          -EventId 500 `
          -EntryType Error `
          -Message $_.Exception.Message
        exit 1
      }
    }
    
    $xl.DisplayAlerts = $false  # prevent Save() method from asking for confirmation
                                # about overwriting existing files (when saving new
                                # workbooks)
                                # WARNING: this might cause data loss!
    
    foreach ($wb in $xl.Workbooks) {
      $wb.Save()
      $wb.Close($false)
    }
    
    $xl.Quit()
    [void][Runtime.InteropServices.Marshal]::ReleaseComObject($xl)
    [GC]::Collect()
    [GC]::WaitForPendingFinalizers()
    

    According to Microsoft's documentation the GetObject() method is supposed to work as well:

    [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
    $xl = [Microsoft.VisualBasic.Interaction]::GetObject($null, 'Excel.Application')
    

    but when I tried it I ended up with an additional (hidden) Excel instance instead of attaching to the already running one.

    Note: If for some reason a user has started multiple Excel instances you will need to run the code once for each instance.

提交回复
热议问题