I try to open and modify an Excel document with a Perl script, and I want this document to be still open after. I use the following code :
unless (defined $ex) {
$ex = Win32::OLE->new('Excel.Application', sub{$_[0]->Quit;})
or die "Cannot start Excel";
}
$ex->{Visible} = 1;
The Excel document briefly appears, then vanishes. The work is correctly done, but Excel closes down after. Is there a way to keep Excel open?
Just remove your destructor construct from the new method:
$ex = Win32::OLE->new('Excel.Application')
or die "Cannot start Excel";
Related
I have Perl script - simple server. Wait for connect in the loop, and write it to the new file on disc. The server should be stopped when ctrl + c are pressed. Before exit, I want to close all files descriptor. It's not so easy, because terminal shows error.
Yes, I read this thread:
How can I check if a filehandle is open in Perl?
But it's not working for me.
There is my main loop:
my $my_socket = new IO::Socket::INET(
LocalHost => $local_host,
LocalPort => $local_port,
Proto => 'tcp',
Listen => 5,
Reuse => 1
);
die "Couldn't create: my_socket $!n " unless $my_socket;
print "Send files.. \n";
while(1) {
my $accepter = $my_socket->accept();
my $count = 0;
#print "$directory.$save_dir/$my_data";
$datetime = localtime();
$datetime =~ s/(?<=\w)\s(?=\w)/_/g;
open my $fh, '>', "$direc/$save_dir/$datetime"
or die "Couldn't open the file";
while(<$accepter>){
chomp;
#last if $count++ ==10;
#print($accepter);
say $fh $_;
}
$SIG{'INT'} = sub {
print "Caught One!\n";
close $fh;
}; #It gives me error. Close fd which is not opened.
}
#print "Received. End \n";
#close $fh;
close $my_socket;
The code in the signal handler closes that filehandle -- and lets the loop continue. So the next time round that filehandle is indeed closed so you get warnings. (I'd expect issues first with printing to it on the next pass, if the problem is described well, but there may be reasons that that's avoided.)
The fix, in short -- set a flag in the signal handler, nothing else. Check for it at a suitable place in the code and if it is set then close the file and exit the loop by last.† (Or perform some other action, as suitable for your code.)
There's more I'd like to comment about the signal handler though. The %SIG is a very global creature. By setting $SIG{INT} you've changed it for all of the code in the interpreter.
Why not use local $SIG{INT} instead? Then it is changed only inside the scope in which it is defined; see local. And I'd pull that definition outside of all loops possible. (If you actually want it global place it right at the beginning so it's loud and clear and not hidden away.)
So something like
SOME_SCOPE:
{
my $got_signal;
local $SIG{INT} = sub {
#say "Caught: #_";
$got_signal = 1;
};
while (1) {
...
open my $fh, '>', ... or die $!;
while (<$accepter>) {
...
if ($got_signal) {
close $fh;
# $got_signal = 0; # if used again (reset it)
last;
}
say $fh $_;
}
...
}
};
This is still a sketch, even as it may work as it stands in a simpler case. I have to assume some things, in the first place that there is a lot more going on in your code. If a contained and complete runnable example can help let me know and I can add it.
† Can't do last in a signal handler's sub (it's not inside of a loop, even if it's defined there in the code). And in general, using last in a sub is not a good idea, to say the least, as it would lead to confusing and opaque code. It also sports very specific behavior
last cannot return a value from a block that typically returns a value, such as eval {}, sub {}, or do {}. It will perform its flow control behavior, which precludes any return value.
(it comes with a warning, too) In this case you don't need to return a value but (even if it worked) you absolutely would not want to do that out of a signal handler.
To note, even as it is linked in the question, that one can check whether a filehandle is open using Scalar::Util::openhandle. That can come handy in this code.
If you want the server to truly exit when Ctrl-C is pressed, simply put an exit statement in the interrupt handler. That will automatically close all the file handles as Perl exits.
If you want the read loop to terminate when Ctrl-C is pressed but the program will otherwise carry on, do this. However might I suggest Ctrl-\ as a better alternative which sends a SIGQUIT signal. Most people don't expect graceful exits from Ctrl-C; they expect crash stop now.
our $doloop = 1;
$SIG{QUIT} = sub { $doloop = 0; };
while ($doloop) { ... }
$SIG{QUIT} = "IGNORE";
cleanup;
exit;
If you need to check if the filehandle is open before trying to close it, just use -e. For other kinds of open tests see perlfunc -X.
close $fh if -e $fh;
HTH
I need to automatize the following operation:
Open a Excel file at a scheduled time
(I configured its data connection so that the data automatically gets
updated)
Save a Copy of that file, WITHOUT including the data connection.
That report is sent to the customer and therefore can't possible include the query code.
The opening, updating and saving as a copy is not a problem and I will do it with a scripting tool that creates a Windows exe-file that then can be launched at the time I schedule it in Windows TaskScheduler.
But how can I manage to eliminate the data connection?
Regard,
Martin
Use a Workbook_Open event, which runs when the file is opened. Then use the scripting tool to open the file and the VBA should run automatically.
If you are using Excel Interop, you can use this code
using Excel = Microsoft.Office.Interop.Excel;
Excel.Application app = new Excel.Application();
app.DisplayAlerts = false;
Excel.Workbook wb = app.Workbooks.Open(filepath);
int count_conn = wb.Connections.Count;
if (count_conn > 0)
{
for (int i = 1; i <= wb.Connections.Count; i++)
{
wb.Connections[i].Delete();
i = i - 1;
}
count_conn = wb.Connections.Count;
wb.Save(); // Save workbook
}
wb.Save();
wb.Close();
app.Quit();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Marshal.ReleaseComObject(wb);
Marshal.ReleaseComObject(app);
}
I've an Excel 2010 spreadsheet with an XML map defined within it. Using Perl I want to save the worksheet as XML Data. I do not need to export the XML map file. From within Excel I can select "File > Save As > Save as type : XML Data". This is the output I want to create, but from my Perl script.
I can output the worksheet in CSV format using the SaveAs command with enum 6. I can also output the spreadsheet in XML format using SaveAs with enum 46, but this is not what I want. I want just the XML Data..
There appears to be a SaveAsXMLData function but I'm unable to get it working. Any help appreciated.
use strict;
use warnings;
use Win32::OLE qw(in with);
use Win32::OLE::Const 'Microsoft Excel';
use Win32::OLE::Variant;
use Win32::OLE::NLS qw(:LOCALE :DATE);
$Win32::OLE::Warn = 3; # Die on Errors.
my $Excel = Win32::OLE->GetActiveObject('Excel.Application')
|| Win32::OLE->new('Excel.Application', 'Quit');
$Excel->{DisplayAlerts}=0;
my $excel_file = 'c:\\temp\\master.xlsx';
my $csv_file = 'c:\\temp\\master.csv';
my $xml_file = 'c:\\temp\\master.xml';
my $workbook = $Excel->Workbooks->Open($excel_file);
# Alt+F11 in Excel to start VBA and after that F2 to start Object browser.
# 6 is CSV format
# 46 is XML spreadsheet
$workbook->SaveAs( $csv_file, 6 );
# Now just the XML Data
# The map is called MDBAC_Map
my $objMapToExport = $Excel->Workbooks->XmlMaps("MDBAC_Map");
$workbook->SaveAsXMLData( $xml_file, $objMapToExport );
$workbook->Close();
$Excel->Quit();
Fixed this myself (I was 99% there!). Using the macro recorder within Excel confirmed the required function calls as follows:
ChDir "C:\temp"
ActiveWorkbook.SaveAsXMLData Filename:="C:\temp\master.xml", Map:= _
ActiveWorkbook.XmlMaps("MDBAC_Map")
The line of code for exporting the XML map is wrong. Changed the above code as follows and the script works fine:
my $objMapToExport = $workbook->XmlMaps("MDBAC_Map");
I am creating Excel Sheet using Devexpress Exporter and then saving the file at a particular location.
After the creation of file, I have to open it, to add dropdownlist of items and then save it again in same location.
After all the operations, the file has to be emailed automatically to the email address from database.
Now if I have 1000 email addresses, and to automate this process, it is creating more than 10 instances of Excel.
How can I stop creation of those instance and how can I use excel operations without using more memory.
Code is as below:
protected string CreateExcelFile(string FilterName)
{
Random ranNumber = new Random();
int number = ranNumber.Next(0, 10000000);
string FileName = "TestDoc"+DateTime.Now.Year.ToString()+number.ToString()+DateTime.Now.Second.ToString()+".xls";
string path = #"c:\TestDocuments\"+FileName;
Directory.CreateDirectory(Path.GetDirectoryName(path));
FileStream fs = new FileStream(path, FileMode.OpenOrCreate);
XlsExportOptions options = new XlsExportOptions();
options.ExportHyperlinks = false;
ASPxExporter.WriteXls(fs, options);
fs.Close();
AddDropDownToExcel(path);
return path;
}
//Adding The Dropdownlist Of Items TO Generated Excel Sheet
protected void AddDropDownToExcel(string path)
{
Microsoft.Office.Interop.Excel.Application application = new Microsoft.Office.Interop.Excel.Application();
string fileName = path.Replace("\\", "\\\\");
string RowCount = "F" + (testgrid.VisibleRowCount + 1).ToString();
// Open Excel and get first worksheet.
var workbook = application.Workbooks.Open(fileName);
var worksheet = workbook.Worksheets[1] as Microsoft.Office.Interop.Excel.Worksheet;
// Set range for dropdownlist
var rangeNewStatus = worksheet.get_Range("F2", RowCount);
rangeNewStatus.ColumnWidth = 20;
rangeNewStatus.Validation.Add(Microsoft.Office.Interop.Excel.XlDVType.xlValidateList, Microsoft.Office.Interop.Excel.XlDVAlertStyle.xlValidAlertStop,
Microsoft.Office.Interop.Excel.XlFormatConditionOperator.xlBetween, "Item1,Item2,Item3,Item4");
// Save.
workbook.Save();
workbook.Close(Microsoft.Office.Interop.Excel.XlSaveAction.xlSaveChanges, Type.Missing, Type.Missing);
application.Quit();
}
First, I sincerely hope this isn't running on a server.
Then, if your problem is that too many instances of Excel are created, a thought is "don't create an instance every single time". Instead of starting Excel every time AddDropDownToExcel is called, can you reuse the same instance?
The problem you are having shows up regularly in Excel interop scenario; even though you are done and tell Excel to close, it "stays alive". It's usually caused by your app still holding a reference to a COM object that hasn't been disposed, preventing Excel from closing. This StackOverflow answer provides some pointers: https://stackoverflow.com/a/158752/114519
In general, to avoid that problem, you want to follow the "one-dot" rule. For instance, in your code:
var workbook = application.Workbooks.Open(fileName);
will be a problem, because an "anonymous" wrapper for Workbooks is created, and will likely not be disposed properly. The "one-dot" rule would say "don't use more than one dot when working with Excel interop", in this case:
var workbooks = application.Workbooks;
var workbook = workbooks.Open(fileName);
A totally different thought - instead of using Interop, can't you use OpenXML to generate your Excel file? I have never tried it to create drop downs, but if it supports it, it will be massively faster than Interop, and the type of problems you have won't happen.
Hope this helps.
As I know the grow of number of runnig excel.exe processes is 'normal' situation to excel :)
The dumbest advice is just kill sometimes it's processes. BUT, this way will be absolutely unhelpful if you use excel during your app is working because of you rather don't get which one excel.exe is yours.
HOW TO close Excel instance started by mail merge
this code running inside launcher does not have access to Excel running via DDE ??
'For i = 1 To Workbooks.Count
' MsgBox ("here" + Workbooks(i).Name)
'If (Workbooks(i).Name <> ActiveWorkbook.Name) Then
'Workbooks(i).Close
'End If
'Next i
You can kill the excel process like so: (from http://www.dreamincode.net/code/snippet1543.htm)
//Namespaces needed
using System.Diagnostics;
public bool FindAndKillProcess(string name)
{
//here we're going to get a list of all running processes on
//the computer
foreach (Process clsProcess in Process.GetProcesses()) {
//now we're going to see if any of the running processes
//match the currently running processes by using the StartsWith Method,
//this prevents us from incluing the .EXE for the process we're looking for.
//. Be sure to not
//add the .exe to the name you provide, i.e: NOTEPAD,
//not NOTEPAD.EXE or false is always returned even if
//notepad is running
if (clsProcess.ProcessName.StartsWith(name))
{
//since we found the proccess we now need to use the
//Kill Method to kill the process. Remember, if you have
//the process running more than once, say IE open 4
//times the loop thr way it is now will close all 4,
//if you want it to just close the first one it finds
//then add a return; after the Kill
clsProcess.Kill();
//process killed, return true
return true;
}
}
//process not found, return false
return false;
}
Excel can be closed via VBA, if called from within Excel
Application.Quit
If called from outside Excel, you will need to set a reference to Excel and then close it.
Set appExcel = GetObject(, "Excel.Application")
appExcel.Quit
You need to ensure that all Workbooks are closed or saved, otherwise Excel will prompt the user to save.