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
@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.