2 # Functions for Windows package management
4 $msi_package_logdir = "$module_config_directory/msis";
5 $sw_discovery_cmd = "$root_directory/SW_Discovery/SW_Discovery.exe";
6 $has_sw_discovery = -x $sw_discovery_cmd;
7 $no_package_filesearch = 1;
9 sub list_package_system_commands
11 return ($sw_discovery_cmd);
14 # list_packages([package]*)
15 # Fills the array %packages with all or listed packages
19 if ($has_sw_discovery) {
20 local %want = map { $_, 1 } @_;
21 open(OUT, "$sw_discovery_cmd |");
25 if (/ID\s*=\s*\{(\S+)\}\s*(\S*)/ ||
28 $packages{$n,'name'} = $1;
29 $packages{$n,'arch'} = 'x86';
30 $packages{$n,'version'} = $2;
31 if (/ID\s*=\s*\{(\S+)\}/) {
32 $packages{$n,'code'} = $1;
35 $packages{$n,'nouninstall'} = 1;
37 $packages{$n,'nolist'} = 1;
39 elsif (/Package\s+Name\s*:\s*(.*)/) {
40 if ($packages{$n,'code'}) {
41 $packages{$n,'name'} = $1;
44 $packages{$n,'desc'} = $1;
47 elsif (/Version\s*:\s*(.*)/) {
48 $packages{$n,'version'} = $1;
50 elsif (/Publisher\s*:\s*(.*)/) {
51 $packages{$n,'vendor'} = $1;
52 $packages{$n,'class'} = $1;
53 $packages{$n,'class'} =~ s/(http|https):\/\///g;
55 elsif (/Location\s*:\s*(\S.*)/) {
56 $packages{$n,'desc'} ||= $1;
58 $packages{$n,'installed'} = $st[9];
64 # Filter out un-wanted packages, or those with no name
65 for(my $i=0; $i<$n; $i++) {
66 if (%want && !$want{$packages{$i,'name'}} ||
67 !$packages{$i,'name'}) {
69 foreach my $f ('name', 'code', 'arch',
71 'vendor', 'installed') {
84 # Query built-in list of those we know were installed
86 @pkgs = &list_msi_packages() if (!@pkgs);
88 foreach my $p (@pkgs) {
89 local $msi = &get_msi_package($p);
90 $packages{$i,'name'} = $msi->{'name'};
91 $packages{$i,'version'} = $msi->{'version'};
92 $packages{$i,'desc'} = $msi->{'desc'};
93 $packages{$i,'nolist'} = 0;
100 # Returns the names of installed MSI packages (using Webmin)
101 sub list_msi_packages
103 opendir(DIR, $msi_package_logdir);
104 local @rv = grep { $_ ne "." && $_ ne ".." } readdir(DIR);
109 # get_msi_package(name)
110 # Returns a hash containing details of an MSI package
115 &read_file("$msi_package_logdir/".lc($pkg), \%msi);
120 # save_msi_package(&package)
121 # Updates the local details of an MSI package
125 &make_dir($msi_package_logdir, 0700);
126 &write_file("$msi_package_logdir/".lc($msi->{'name'}), $msi);
129 # delete_msi_package(name)
130 # Removes the local details of an MSI package
131 sub delete_msi_package
134 unlink("$msi_package_logdir/".lc($name));
137 # package_info(package, [version])
138 # Returns an array of package information in the order
139 # name, class, description, arch, version, vendor, installtime,
140 # no-uninstall, no-list
143 local ($name, $ver) = @_;
144 if ($has_sw_discovery) {
145 # Get from total list
147 local $n = &list_packages();
148 for(my $i=0; $i<$n; $i++) {
149 if ($packages{$i,'name'} eq $name &&
150 (!$ver || $ver eq $packages{$i,'version'})) {
151 return ($packages{$i,'name'},
152 $packages{$i,'vendor'},
153 $packages{$i,'desc'},
154 $packages{$i,'arch'},
155 $packages{$i,'version'},
156 $packages{$i,'vendor'},
157 $packages{$i,'installed'} ?
158 &make_date($packages{$i,'installed'}) :
160 $packages{$i,'nouninstall'},
161 $packages{$i,'nolist'},
168 # Query our savedd DB
169 local $msi = &get_msi_package($name);
170 return ( $msi->{'name'}, $msi->{'class'}, $msi->{'desc'},
171 $msi->{'arch'}, $msi->{'version'}, $msi->{'vendor'},
172 &make_date($msi->{'installed'}),
173 $msi->{'code'} ? 1 : 0,
179 # Check if some file is a package file
183 return $file =~ /\.msi$/i ? 1 : 0;
186 # file_packages(file)
187 # Returns a list of all packages in the given file, in the form
188 # package-version description
189 # XXX how to get proper description?
193 if ($file =~ /([^\/\\]+)\.msi$/i) {
195 $suffix =~ s/_\d.*$//; # For files named like apache_2.x.y...
201 # install_options(file, package)
202 # Outputs HTML for choosing install options for some package
205 local ($file, $pkg) = @_;
206 print &ui_table_row($text{'msi_users'},
207 &ui_radio("users", 2, [ [ 0, $text{'msi_users0'} ],
208 [ 1, $text{'msi_users1'} ],
209 [ 2, $text{'msi_users2'} ] ]), 3);
212 # install_package(file, package, [&inputs])
213 # Install the given package from the given file, using options from %in. Returns
214 # undef on success or an error message on failure.
217 local ($file, $pkg, $in) = @_;
219 # Run the msiexec command
220 local $temp = &tempname();
223 local @st = stat($file);
224 system("msiexec /i "."e_path($file)." /quiet /norestart ".
225 "/l*vx "."e_path($temp)." >$null_file");
226 &wait_till_stopped_logging($temp);
228 # Read output from log file
230 local %msi = ( 'name' => $pkg,
231 'arch' => 'x86', # Wrong!
232 'installed' => time() );
237 s/\0//g; # Strip unicode, primitively
238 if (/Product:\s*(.*)\s\-\-.*success/i) {
241 elsif (/ProductVersion\s*=\s*(.*)/) {
242 $msi{'version'} = $1;
244 elsif (/ProductName\s*=\s*(.*)/) {
247 elsif (/ProductCode\s*=\s*(.*)/) {
250 elsif (/Manufacturer\s*=\s*(.*)/) {
253 elsif (/Note:\s+\d+:\s+\d+\s+\d+:\s+([a-z]:.*)/) {
254 $msi{'files_'.$fc} = $1;
255 local @st = stat($1);
256 $msi{'sizes_'.$fc} = $st[7];
262 return "MSI install failed!";
264 &save_msi_package(\%msi);
266 # Make available to users, if requested
267 if ($in->{'users'}) {
268 local $flag = $in->{'users'} == 1 ? "u" : "m";
269 system("msiexec /j$flag "."e_path($file)." /quiet /norestart ".
276 # check_files(package, version)
277 # Fills in the %files array with information about the files belonging
278 # to some package. Values in %files are path type user group size error
281 local ($name, $ver) = @_;
282 local $msi = &get_msi_package($name);
284 for($i=0; defined($msi->{'files_'.$i}); $i++) {
285 $files{$i,'path'} = $msi->{'files_'.$i};
286 local @st = stat($files{$i,'path'});
287 $files{$i,'type'} = -d $files{$i,'path'} ? 1 : 0;
288 $files{$i,'size'} = $msi->{'sizes_'.$i};
290 $files{$i,'error'} = $text{'msi_missing'};
292 elsif ($files{$i,'size'} ne $st[7]) {
293 $files{$i,'error'} = $text{'msi_size'};
299 # delete_options(package)
300 # Outputs HTML for package uninstall options
307 # delete_package(package, [&options], version)
308 # Attempt to remove some package
311 local ($name, $opts, $ver) = @_;
313 # Call the uninstall command
315 if ($has_sw_discovery) {
316 # Get code form package list
318 local $n = &list_packages();
319 for(my $i=0; $i<$n; $i++) {
320 if ($packages{$i,'name'} eq $name &&
321 !$ver || $ver eq $packages{$i,'version'}) {
322 $code = $packages{$i,'code'};
325 $code || &error("Couldn't find install code for $name");
328 local $msi = &get_msi_package($name);
329 $code = $msi->{'code'};
331 local $temp = &tempname();
333 system("msiexec /x \"$code\" /quiet /norestart ".
334 "/l*vx "."e_path($temp)." >$null_file");
335 &wait_till_stopped_logging($temp);
337 # Check log for success
342 s/\0//g; # Strip unicode, primitively
343 if (/Product:\s*(.*)\s\-\-.*success/i) {
349 return "MSI uninstall failed";
352 # Remove from local info
353 &delete_msi_package($name);
368 # wait_till_stopped_logging(file)
369 # Spin until some file has remained untouched for 5 seconds
370 sub wait_till_stopped_logging
374 local @before = stat($file);
376 local @after = stat($file);
377 last if ($before[9] == $after[9]);