For the past week, I have been porting to perl a VB application used to automate the data refresh of an excel workbook containing one or more spreadsheets, each of which may contain any number query tables. During this process, the following problem presented itself: How can I tell when excel has finished refreshing all of the queries contained within the workbook? Having failed to find any examples pertaining to this specific problem, either within PM or without, I humbly present this (simplified) solution for consideration.
#!c:/perl/bin use strict; use warnings; use Win32::OLE qw(in with); #I strongly recommend a user defined error handler when #using Win32::OLE with excel and refreshAll. You must #have a way to handle the Application Busy error that #occurs when Microsoft Query is holding excel hostage. Win32::OLE->Option(Warn => sub {&CHECK_OLE_ERROR;}); my $filename = "my_book.xls"; my $sleep_time = 30; #I always want a new instance of excel, so that I can turn off all use +r interaction my $excel = Win32::OLE->new('Excel.Application', 'Quit') or die "Unabl +e to open Excel"; #For my purposes, this application runs behind the #scenes and should never be interacting with a user. #Hence I turn off the following properties. $excel->{Application}->{DisplayAlerts} = 0; $excel->{Visible} = 0; my $book = $excel->Workbooks->Open("$filename")or die "Unable to open +$filename"; $book->RefreshAll; #refreshAll does not having a meaningful return val +ue, to the best of my knowledge until(&REFRESH_COMPLETE($book)){ sleep $sleep_time; } $book->Save or warn "Unable to save changes to $filename!"; $book->Close or warn "Unable to gracefully close $filename!"; undef $book; undef $excel; sub REFRESH_COMPLETE{ #this code has been moved into a subroutine because #each loop would periodically return undef prior to #completly traversing its hash. I have not been able #to fully determine the cause of this, short of believing #it occurs when OLE hits an Application Busy exception. #By moving the code to a subroutine, I am able to #safely reset the loop whenever an undef is encountered. #I am very interested in any more elegant solutions #to this problem. my $book = pop(@_); #for each worksheet in the workbook foreach my $sheet (in $book->{Sheets}){ return undef unless defined $sheet; #look for any queries in this worksheet foreach my $qryTable (in $sheet->{QueryTables}){ return undef unless defined $qryTable; return undef if $qryTable->{Refreshing} > 0; } } return 1; } sub CHECK_OLE_ERROR{ my $status = Win32::OLE->LastError(); #application busy--just give it some time if($status =~ /0x8001010a/){ sleep $sleep_time; return 1; }else{ die Win32::OLE->LastError(); } }
Further details available
The full version of this application runs eternally on a win2003 server. It reads a list of reports to refresh and does so at the appropriate time (typically triggered by the presence of a "trigger" file). Most reports update charts in the first few pages of the workbook. As each workbook is refreshed it mails either the workbook or a pdf (thank you http://www.ghostscript.com/awki) to the appropriate persons as determined by the .ini file.

Replies are listed 'Best First'.
Re: RFC: Win32::OLE and Excel's RefreshAll
by thoglette (Scribe) on Dec 05, 2007 at 05:43 UTC
    I'm sitting here with exactly the same problem (in this case complicated by the Excel spreadsheet modifying itself and/or being openned by other processes).

    So I'm also struggling with trying to translate Open(ReadOnly:=True) and Close(SaveChanges:=False).

    And deal with the file-already-open problems



    Butlerian Jihad now!
      If you can describe the problem you're having in a little more detail, I may be able to help.
        Sorry for the delay - had some more visible fires to extinguish.

        I have an XLS from which I'm generating XML (and then HTML etc etc) The catches are two fold

        1. the XLS has some convoluted functions which mean that openning it changes it.
        2. the XLS may be currently open by a human user (adding or correcting data)
        So I need to either open-it read-only and/or close it with don't save. Now I believe that the correct VB is Open(ReadOnly:=True) and/or Close(SaveChanges:=False). But so far(*) I've been unable to convert this into an effective representation in Perl

        * I've only had time to put about an hour into this problem - as it works OK except in the cases listed above.



        Butlerian Jihad now!