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.
Set xlBook = GetObject("C:\Users\David Candy\Documents\Super.xls", "Excel.Application")
For each wsheet in xlbook.worksheets
msgbox wsheet.name
next
VBScript, JScript, PS, VBA can all do it. Above is VBScript (and thus also VBA). The technology is COM not a language.
EDIT
As said using the app object doesn't work in excel.
A excel file opened.
EXCEL.EXE 13560 Console 1 19,228 K Running DESKTOP-UCDGI39\David Candy
0:00:00 Microsoft Excel - Super.xls
Running GetObject using Excel.Application
EXCEL.EXE 13560 Console 1 19,236 K Running DESKTOP-UCDGI39\David Candy
0:00:00 Microsoft Excel - Super.xls
EXCEL.EXE 15124 Console 1 12,772 K Running DESKTOP-UCDGI39\David Candy
0:00:00 N/A
NOTE
This is in addition to other faulty behaviour by excel where it won't exit memory when references are released.