File Coverage

blib/lib/Win32/Exe.pm
Criterion Covered Total %
statement 34 37 91.8
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 46 49 93.8


line stmt bran cond sub pod time code
1             package Win32::Exe;
2             $Win32::Exe::VERSION = '0.17';
3              
4             =head1 NAME
5              
6             Win32::Exe - Manipulate Win32 executable files
7              
8             =head1 VERSION
9              
10             This document describes version 0.17 of Win32::Exe, released
11             July 19, 2011.
12              
13             =head1 SYNOPSIS
14              
15             use Win32::Exe;
16             my $exe = Win32::Exe->new('c:/windows/notepad.exe');
17            
18             # add a default resource structure if none exists
19             # create_resource_section only works on MSWin and
20             # does not work on Windows XP - requires Vista or
21             # above
22            
23             $exe = $exe->create_resource_section if $exe->can_create_resource_section;
24              
25             # Get version information
26             my $info = $exe->get_version_info;
27             print qq($_ = $info->{$_}\n) for (sort keys(%$info));
28              
29             # Extract icons from an executable
30             my @iconnames = $exe->get_group_icon_names;
31             for ( my $i = 0; $i < @iconnames; $i++ ) {
32             my $filename = 'icon' . $i . '.ico';
33             my $iconname = $iconnames[$i];
34             $exe->extract_group_icon($iconname,$filename);
35             }
36              
37             # Import icons from a .exe or .ico file and write back the file
38             $exe->update( icon => '/c/windows/taskman.exe' );
39             $exe->update( icon => 'myicon.ico' );
40              
41             # Change it to a console application, then save to another .exe
42             $exe->set_subsystem_console;
43             $exe->write('c:/windows/another.exe');
44            
45             # Add a manifest section
46             $exe->update( manifest => $mymanifestxml );
47             # or a default
48             $exe->update( defaultmanifest => 1 );
49            
50             # or specify manifest args
51             $exe->update( manifestargs => { ExecLevel => 'requireAdministrator' } );
52            
53             # Get manifest object
54             $manifest = $exe->get_manifest if $exe->has_manifest;
55            
56             # change execution level
57             $manifest->set_execution_level('requireAdministrator');
58             $exe->set_manifest($manifest);
59             $exe->write;
60            
61              
62             =head1 DESCRIPTION
63              
64             This module parses and manipulating Win32 PE/COFF executable headers,
65             including version information, icons, manifest and other resources.
66             The module Win32::Exe::Manifest can be used for manifest handling.
67              
68             A script exe_update.pl is provided for simple file updates.
69              
70             Also, please see the test files in the source distributions
71             F directory for examples of using this module.
72              
73             =head1 METHODS
74              
75             =head2 new
76              
77             my $exe = Win32::Exe->new($filename);
78              
79             Create a Win32::Exe object from $filename. Filename can be an executable
80             or a DLL.
81              
82             =head2 update
83              
84             $exe->update( icon => 'c:/my/icon.ico',
85             gui => 1,
86             info => [ 'FileDescription=My File', 'FileVersion=1.4.3.3567' ],
87             manifest => 'c:/my/manifest.xml',
88             manifestargs => [ 'ExecLevel=asInvoker', 'CommonControls=1' ],
89             );
90              
91             The B method provides a convenience method for the most common actions. It
92             writes the information you provide to the file opened by Win32::Exe->new($filename).
93             You do not have to call $exe->write - the method automatically updates the opened
94             file.
95              
96             Param detail:
97              
98             B Pass the name of an executable, dll or ico file to extract the icon and
99             make it the main icon for the Win32 executable.
100              
101             B Pass a reference to an array of strings containing key - value pairs
102             separated by '='.
103              
104             e.g. info => [ 'FileDescription=My File', 'FileVersion=1.4.3.3567' ]
105              
106             Recognised keys are
107              
108             Comments CompanyName FileDescription FileVersion
109             InternalName LegalCopyright LegalTrademarks OriginalFilename
110             ProductName ProductVersion
111              
112             B Use parameter 'gui' or 'console' to set the executable
113             subsystem to Windows or Console. You can, of course, only use one or the other
114             of gui / console, not both.
115              
116             B Specify a manifest file to add to the executable resources.
117              
118             B As an alternative to specifying a manifest file, pass a
119             reference to an array of strings containing key - value pairs separated
120             by '='.
121              
122             e.g. manifestargs => [ 'ExecLevel=asInvoker', 'CommonControls=1' ]
123              
124             Recognised keys are
125              
126             ExecutionLevel UIAccess ExecName Description
127             CommonControls Version
128              
129             =head2 create_resource_section
130              
131             $exe = $exe->create_resource_section if $exe->can_create_resource_section;
132              
133             If an executable file does not have an existing resource section, you must create
134             one before attempting to add version, icon or manifest resources. The method
135             create_resource_section is only available on MSWin platforms. Also, the method
136             will fail if your windows version is Windows XP or below. You can check if it is
137             possible to call create_resource_section by calling $exe->can_create_resource_section.
138             After calling create_resource_section, the original Win32::Exe object does not
139             reference the updated data. The method therefore returns a reference to a new
140             Win32::Exe object that references the updated data.
141              
142             Always call as :
143              
144             $exe = $exe->create_resource_section if $exe->can_create_resource_section;
145              
146             if the $exe already has a resource section, this call will safely return a reference
147             to the original object without updating the original exe.
148              
149             =head2 can_create_resource_section
150              
151             $exe = $exe->create_resource_section if $exe->can_create_resource_section;
152              
153             Check if the operating system and version allow addition of a resource section
154             when none exists in the target executable.
155              
156             =head2 get_manifest
157              
158             my $manifest = $exe->get_manifest;
159              
160             Retrieves a Win32::Exe::Manifest object. (See docs for Win32::Exe::Manifest)
161              
162             =head2 set_manifest
163              
164             $exe->set_manifest($manifest);
165             $exe->write;
166            
167             Takes a Win32::Exe::Manifest object. You must explicitly call 'write' to commit
168             changes to file. Also takes a filepath to an xml file containing raw manifest
169             information.
170              
171             =head2 set_manifest_args
172              
173             $exe->set_manifest_args($argref);
174             $exe->write;
175              
176             Accepts a reference to a hash with one or more of the the keys
177              
178             ExecutionLevel UIAccess ExecName Description
179             CommonControls Version
180              
181             Also accepts a reference to an array of strings of the format:
182             [ 'key1=value1', 'key2=value2' ]
183              
184             Example Values:
185              
186             ExecutionLevel=asInvoker
187             UIAccess=false
188             CommonControls=1
189             Version=6.8.67.334534
190             ExecName=My.Application
191             Description=My Application
192              
193             The CommonControls argument can be specified to add a dependency on
194             Common Controls Library version 6.0.0.0
195              
196             =head2 get_version_info
197              
198             my $inforef = $exe->get_version_info;
199              
200             Returns a reference to a hash with the keys:
201              
202             Comments CompanyName FileDescription FileVersion
203             InternalName LegalCopyright LegalTrademarks OriginalFilename
204             ProductName ProductVersion
205              
206             =head2 set_version_info
207              
208             $exe->set_version_info($inforef);
209             $exe->write;
210              
211             Accepts a reference to a hash with one or more of the the keys
212              
213             Comments CompanyName FileDescription FileVersion
214             InternalName LegalCopyright LegalTrademarks OriginalFilename
215             ProductName ProductVersion
216              
217             Also accepts a reference to an array of strings of the format:
218             [ 'key1=value1', 'key2=value2' ]
219              
220             =head2 set_subsystem_windows
221              
222             $exe->set_subsystem_windows;
223             $exe->write;
224              
225             Sets the executable system as 'windows'. (GUI).
226             You may also call $exe->SetSubsystem('windows);
227             This is the equivalent of $exe->update( gui => 1);
228              
229             =head2 set_subsystem_console
230              
231             $exe->set_subsystem_console;
232             $exe->write;
233              
234             Sets the executable system as 'console'.
235             You may also call $exe->SetSubsystem('console);
236             This is the equivalent of $exe->update( console => 1);
237              
238             =head2 get_subsystem
239              
240             my $subsys = $exe->get_subsystem;
241              
242             Returns a descriptive string for the subsystem.
243             Possible values: windows | console | posix | windowsce | native
244             You can usefully update executables with the windows or console
245             subsystem
246              
247             =head2 set_single_group_icon
248              
249             $exe->set_single_group_icon($iconfile);
250             $exe->write;
251              
252             Accepts the path to an icon file. Replaces all the icons in the
253             exec with the icons from the file.
254              
255             =head2 get_group_icon_names
256              
257             my @iconnames = $exe->get_group_icon_names;
258              
259             Returns a list of the names of all the group icons in the
260             executable or dll. If there are no group icons, returns an empty
261             list. The names returned can be used as parameters to other icon
262             handling methods.
263              
264             =head2 get_group_icon
265              
266             $exe->get_group_icon($groupiconname);
267              
268             Accepts a group icon name as returned by get_group_icon_names.
269             Returns the Wx::Exe::Resource::GroupIcon object for the named
270             GroupIcon
271              
272             =head2 add_group_icon
273              
274             $exe->add_group_icon($groupiconname, $iconfilepath);
275             $exe->write;
276              
277             Accepts a group icon name and a path to an icon file. Adds the
278             icon to the exec without affecting existing icons. The group
279             icon name must not already exist.
280              
281             =head2 replace_group_icon
282              
283             $exe->replace_group_icon($groupiconname, $iconfilepath);
284             $exe->write;
285              
286             Accepts a group icon name and a path to an icon file.
287             Replaces the groupicon named with the contents of the icon file.
288              
289             =head2 remove_group_icon
290              
291             $exe->remove_group_icon($groupiconname);
292             $exe->write;
293              
294             Accepts a group icon name. Removes the group icon with that name
295             from the exec or dll.
296              
297             =head2 export_group_icon
298              
299             $exe->export_group_icon($groupiconname, $iconfilepath);
300              
301             Accepts a group icon name and a .ico filepath. Writes the named
302             icon group to the file in filepath.
303              
304              
305             =cut
306              
307 4     4   69469 use strict;
  4         7  
  4         178  
308 4     4   28 use base 'Win32::Exe::Base';
  4         9  
  4         2385  
309 4         476 use constant FORMAT => (
310             Magic => 'a2', # "MZ"
311             _ => 'a58',
312             PosPE => 'V',
313             _ => 'a{($PosPE > 64) ? $PosPE - 64 : "*"}',
314             PESig => 'a4',
315             Data => 'a*',
316 4     4   23 );
  4         83  
317 4         188 use constant DELEGATE_SUBS => (
318             'IconFile' => [ 'dump_iconfile', 'write_iconfile' ],
319 4     4   23 );
  4         7  
320 4     4   19 use constant DISPATCH_FIELD => 'PESig';
  4         8  
  4         298  
321             use constant DISPATCH_TABLE => (
322             "PE\0\0" => "PE",
323 0         0 '*' => sub { die "Incorrect PE header -- not a valid .exe file" },
324 4     4   19 );
  4         19  
  4         216  
325 4     4   19 use constant DEBUG_INDEX => 6;
  4         10  
  4         233  
326 4     4   21 use constant DEBUG_ENTRY_SIZE => 28;
  4         16  
  4         184  
327              
328 4     4   22 use File::Basename ();
  4         7  
  4         78  
329 4     4   2342 use Win32::Exe::IconFile;
  4         9  
  4         128  
330 4     4   3387 use Win32::Exe::DebugTable;
  4         11  
  4         200  
331 4     4   3661 use Win32::Exe::Manifest;
  0            
  0            
332              
333             sub is_application { ( $_[0]->is_assembly ) ? 0 : 1; }
334              
335             sub is_assembly { $_[0]->Characteristics & 0x2000; }
336              
337             sub has_resource_section {
338             my ($self) = @_;
339             my $section = $self->first_member('Resources');
340             return( $section ) ? 1 : 0;
341             }
342              
343             sub can_create_resource_section {
344             my $self = shift;
345             return 0 if ($^O !~ /^mswin/i );
346             require Win32;
347             my ($winstring, $winmajor, $winminor, $winbuild, $winid) = Win32::GetOSVersion();
348             return ( $winmajor > 5 ) ? 1 : 0;
349             }
350              
351             sub create_resource_section {
352             my $self = shift;
353             return $self if($self->has_resource_section || ( $^O !~ /^mswin/i ) );
354             if(!$self->can_create_resource_section) {
355             die('Cannot create resource section on this version of Windows');
356             }
357             require Win32::Exe::InsertResourceSection;
358             my $filename = (exists($self->{filename}) && (-f $self->{filename} )) ? $self->{filename} : undef;
359             return $self if !$filename;
360             if(my $newref = Win32::Exe::InsertResourceSection::insert_pe_resource_section($filename)) {
361             return $newref;
362             } else {
363             return $self;
364             }
365             }
366              
367             sub resource_section {
368             my ($self) = @_;
369             my $section = $self->first_member('Resources');
370             return $section if $section;
371             my $wmsg = 'No resource section found in file ';
372             $wmsg .= $self->{filename} if(exists($self->{filename}) && $self->{filename});
373             warn $wmsg;
374             return undef;
375             }
376              
377             sub sections {
378             my ($self) = @_;
379             my $method = (wantarray ? 'members' : 'first_member');
380             return $self->members('Section');
381             }
382              
383             sub data_directories {
384             my ($self) = @_;
385             return $self->members('DataDirectory');
386             }
387              
388             sub update_debug_directory {
389             my ($self, $boundary, $extra) = @_;
390              
391             $self->SetSymbolTable( $self->SymbolTable + $extra )
392             if ($boundary <= $self->SymbolTable);
393              
394             my @dirs = $self->data_directories;
395             return if DEBUG_INDEX > $#dirs;
396              
397             my $dir = $dirs[DEBUG_INDEX] or return;
398             my $size = $dir->Size;
399             my $addr = $dir->VirtualAddress;
400              
401             return unless $size or $addr;
402              
403             my $count = $size / DEBUG_ENTRY_SIZE or return;
404              
405             (($size % DEBUG_ENTRY_SIZE) == 0) or return;
406              
407             foreach my $section ($self->sections) {
408             my $offset = $section->FileOffset;
409             my $f_size = $section->FileSize;
410             my $v_addr = $section->VirtualAddress;
411              
412             next unless $v_addr <= $addr;
413             next unless $addr < ($v_addr + $f_size);
414             next unless ($addr + $size) < ($v_addr + $f_size);
415              
416             $offset += $addr - $v_addr;
417             my $data = $self->substr($offset, $size);
418              
419             my $table = Win32::Exe::DebugTable->new(\$data);
420              
421             foreach my $dir ($table->members) {
422             next unless $boundary <= $dir->Offset;
423              
424             $dir->SetOffset($dir->Offset + $extra);
425             $dir->SetVirtualAddress($dir->VirtualAddress + $extra)
426             if $dir->VirtualAddress > 0;
427             }
428              
429             $self->substr($offset, $size, $table->dump);
430             last;
431             }
432             }
433              
434             sub default_info {
435             my $self = shift;
436              
437             my $filename = File::Basename::basename($self->filename);
438              
439             return join(';',
440             "CompanyName= ",
441             "FileDescription= ",
442             "FileVersion=0.0.0.0",
443             "InternalName=$filename",
444             "LegalCopyright= ",
445             "LegalTrademarks= ",
446             "OriginalFilename=$filename",
447             "ProductName= ",
448             "ProductVersion=0.0.0.0",
449             );
450             }
451              
452             sub update {
453             my ($self, %args) = @_;
454            
455             if ($args{defaultmanifest}) {
456             $self->add_default_manifest();
457             }
458            
459             if (my $manifest = $args{manifest}) {
460             $self->set_manifest($manifest);
461             }
462            
463             if (my $manifestargs = $args{manifestargs}) {
464             $self->set_manifest_args($manifestargs);
465             }
466              
467             if (my $icon = $args{icon}) {
468             my @icons = Win32::Exe::IconFile->new($icon)->icons;
469             $self->set_icons(\@icons) if @icons;
470             }
471              
472             if (my $info = $args{info}) {
473             $self->set_version_info( $info);
474             }
475              
476             die "'gui' and 'console' cannot both be true"
477             if $args{gui} and $args{console};
478              
479             $self->SetSubsystem("windows") if $args{gui};
480             $self->SetSubsystem("console") if $args{console};
481             $self->write;
482             }
483              
484             sub icons {
485             my ($self) = @_;
486             my $rsrc = $self->resource_section or return;
487             my @icons = map $_->members, $rsrc->objects('GroupIcon');
488             wantarray ? @icons : \@icons;
489             }
490              
491             sub set_icons {
492             my ($self, $icons) = @_;
493              
494             my $rsrc = $self->resource_section;
495             my $name = eval { $rsrc->first_object('GroupIcon')->PathName }
496             || '/#RT_GROUP_ICON/#1/#0';
497              
498             $rsrc->remove('/#RT_GROUP_ICON');
499             $rsrc->remove('/#RT_ICON');
500              
501             my $group = $self->require_class('Resource::GroupIcon')->new;
502             $group->SetPathName($name);
503             $group->set_parent($rsrc);
504             $rsrc->insert($group->PathName, $group);
505              
506             $group->set_icons($icons);
507             $group->refresh;
508             }
509              
510             sub version_info {
511             my ($self) = @_;
512             my $rsrc = $self->resource_section or return;
513              
514             # XXX - return a hash in list context?
515              
516             return $rsrc->first_object('Version');
517             }
518              
519             sub get_version_info {
520             my $self = shift;
521             my $vinfo = $self->version_info or return;
522             my @keys = qw(
523             Comments CompanyName FileDescription FileVersion
524             InternalName LegalCopyright LegalTrademarks OriginalFilename
525             ProductName ProductVersion
526             );
527             my $rval = {};
528             for my $key (@keys) {
529             my $val = $vinfo->get($key);
530             $val =~ s/,/\./g if(defined($val) && ( $key =~ /version/i ) );
531             $rval->{$key} = $val if defined($val);
532             }
533             return $rval
534             }
535              
536             sub set_version_info {
537             my ($self, $inputpairs) = @_;
538             my $inputref;
539             if(ref($inputpairs) eq 'HASH') {
540             my @newinput = ();
541             push(@newinput, qq($_=$inputpairs->{$_})) for (sort keys(%$inputpairs));
542             $inputref = \@newinput;
543             } else {
544             $inputref = $inputpairs;
545             }
546             my @info = ($self->default_info, @$inputref);
547             my @pairs;
548             foreach my $pairs (map split(/\s*;\s*(?=[\w\\\/]+\s*=)/, $_), @info) {
549             my ($key, $val) = split(/\s*=\s*/, $pairs, 2);
550             next if $key =~ /language/i;
551              
552             if ($key =~ /^(product|file)version$/i) {
553             $key = "\u$1Version";
554             $val =~ /^(?:\d+\.)+\d+$/ or die "$key cannot be '$val'";
555             $val .= '.0' while $val =~ y/.,// < 3;
556              
557             push(@pairs,
558             [ $key => $val ],
559             [ "/StringFileInfo/#1/$key", $val ]);
560             } else {
561             push(@pairs, [ $key => $val ]);
562             }
563             }
564             my $rsrc = $self->resource_section or return;
565             my $version = $rsrc->first_object('Version') or return;
566             $version->set(@$_) for @pairs;
567             $version->refresh;
568             }
569              
570             sub manifest {
571             my ($self) = @_;
572             my $rsrc = $self->resource_section or return;
573             if( my $obj = $rsrc->first_object('Manifest') ) {
574             return $obj;
575             } else {
576             return $self->require_class('Resource::Manifest')->new;
577             }
578             }
579              
580             sub has_manifest {
581             my ($self) = @_;
582             my $rsrc = $self->resource_section or return 0;
583             if( my $obj = $rsrc->first_object('Manifest') ) {
584             return 1;
585             } else {
586             return 0;
587             }
588             }
589              
590             sub set_manifest {
591             my ($self, $input) = @_;
592             # support code that passes xml, filepaths and objects
593             my $resid = 0;
594             my $xml;
595             if(ref($input) && $input->isa('Win32::Exe::Manifest')) {
596             $resid = $input->get_resource_id;
597             $xml = $input->output ;
598             } else {
599             my $filecontent;
600             eval {
601             my $paramisfile = 0;
602             {
603             no warnings qw( io );
604             $paramisfile = (-f $input);
605             }
606             if($paramisfile) {
607             open my $fh, '<', $input;
608             $filecontent = do { local $/; <$fh> };
609             my $errors = $@;
610             close($fh);
611             die $errors if $errors;
612             } else {
613             $filecontent = $input;
614             }
615             };
616             $xml = ( $@ ) ? $input : $filecontent;
617             }
618             $resid ||= 1;
619             my $rsrc = $self->resource_section;
620             my $name = '/#RT_MANIFEST/#' . $resid . '/#0';
621             $rsrc->remove("/#RT_MANIFEST");
622             my $manifest = $self->require_class('Resource::Manifest')->new;
623             $manifest->SetPathName( $name );
624             $manifest->set_parent( $rsrc );
625             $manifest->update_manifest( $xml );
626             $rsrc->insert($manifest->PathName, $manifest);
627             $rsrc->refresh;
628             }
629              
630             sub set_manifest_args {
631             my ($self, $inputpairs) = @_;
632             my $inputref;
633             if(ref($inputpairs eq 'HASH')) {
634             my @newinput = ();
635             push(@newinput, qq($_ = $inputpairs->{$_})) for (sort keys(%$inputpairs));
636             $inputref = \@newinput;
637             } else {
638             $inputref = $inputpairs;
639             }
640             my @manifestargs = @$inputpairs;
641             my %arghash;
642             foreach my $pairs (map split(/\s*;\s*(?=[\w\\\/]+\s*=)/, $_), @manifestargs) {
643             my ($key, $val) = split(/\s*=\s*/, $pairs, 2);
644             my $addkey = lc($key);
645             $arghash{$addkey} = $val;
646             }
647             my $manifest = $self->get_manifest;
648             $manifest->set_execution_level($arghash{executionlevel}) if exists($arghash{executionlevel});
649             $manifest->set_uiaccess($arghash{uiaccess}) if exists($arghash{uiaccess});
650             $manifest->set_assembly_name($arghash{execname}) if exists($arghash{execname});
651             $manifest->set_assembly_description($arghash{description}) if exists($arghash{description});
652             $manifest->set_assembly_version($arghash{version}) if exists($arghash{version});
653             $manifest->add_common_controls() if $arghash{commoncontrols};
654            
655             $self->set_manifest($manifest);
656             }
657              
658             sub get_manifest {
659             my ($self) = @_;
660             my $mtype = ($self->is_assembly) ? 'assembly' : 'application';
661             my $mfestxml = $self->manifest->get_manifest;
662             my $mfest = Win32::Exe::Manifest->new($mfestxml, $mtype);
663             $mfest->set_resource_id( $self->manifest->get_manifest_id );
664             return $mfest;
665             }
666              
667             sub add_default_manifest {
668             my ($self) = @_;
669             my $rsrc = $self->resource_section;
670             my $name = '/#RT_MANIFEST/#1/#0';
671             $rsrc->remove("/#RT_MANIFEST");
672             my $manifest = $self->require_class('Resource::Manifest')->new;
673             my $xml = $manifest->default_manifest;
674             $manifest->SetPathName( $name );
675             $manifest->set_parent( $rsrc );
676             $manifest->update_manifest( $xml );
677             $rsrc->insert($manifest->PathName, $manifest);
678             $rsrc->refresh;
679             }
680              
681             sub merge_manifest {
682             my ($self, $mnf) = @_;
683             return if !(ref($mnf) && $mnf->isa('Win32::Exe::Manifest'));
684             my $main = $self->get_manifest;
685             $main->merge_manifest($mnf);
686             $self->set_manifest($main);
687             }
688              
689             sub set_subsystem_windows {
690             my $self = shift;
691             return if !$self->is_application;
692             $self->SetSubsystem("windows")
693             }
694              
695             sub set_subsystem_console {
696             my $self = shift;
697             return if !$self->is_application;
698             $self->SetSubsystem("console")
699             }
700              
701             sub get_subsystem { $_[0]->Subsystem; }
702              
703             sub get_group_icon_names {
704             my $self = shift;
705             my @names = ();
706             my $section = $self->resource_section or return @names;
707             for my $resource ( $section->objects('GroupIcon')) {
708             my $path = $resource->PathName;
709             my($_null, $_rtgi, $name, $_langid) = split(/\//, $path);
710             push(@names, $name);# if $resource->isa('Win32::Exe::Resource::GroupIcon');
711             }
712             return @names;
713             }
714              
715             sub set_single_group_icon {
716             my($self, $iconfile) = @_;
717             my @icons = Win32::Exe::IconFile->new($iconfile)->icons;
718             $self->set_icons(\@icons) if @icons;
719             }
720              
721             sub get_group_icon {
722             my ($self, $getname) = @_;
723             return undef if !$getname;
724             my $res = undef;
725             my $section = $self->resource_section or return $res;
726             $res = $self->_exists_group_icon($getname);
727             return $res;
728             }
729              
730             sub add_group_icon {
731             my ($self, $newname, $filename) = @_;
732             my $exists = 0;
733             my $section = $self->resource_section or return;
734             my ($res, $langid) = $self->_exists_group_icon($newname);
735             return undef if $res; # it already exists
736            
737             my @icons = Win32::Exe::IconFile->new($filename)->icons;
738             return if !(scalar @icons);
739            
740             my $group = $self->require_class('Resource::GroupIcon')->new;
741             my $pathname = '/#RT_GROUP_ICON/' . $newname . '/' . $langid;
742             $group->SetPathName($pathname);
743             $group->set_parent($section);
744             $section->insert($group->PathName, $group);
745             $group->set_icons(\@icons);
746             $group->refresh;
747             }
748              
749             sub replace_group_icon {
750             my ($self, $getname, $filename) = @_;
751             my $section = $self->resource_section or return;
752             my @icons = Win32::Exe::IconFile->new($filename)->icons;
753             return if !(scalar @icons);
754             my $group = $self->get_group_icon($getname) or return;
755             my $pathname = $group->PathName;
756             $section->remove($pathname);
757             my $newgroup = $self->require_class('Resource::GroupIcon')->new;
758             $newgroup->SetPathName($pathname);
759             $newgroup->set_parent($section);
760             $section->insert($newgroup->PathName, $newgroup);
761             $newgroup->set_icons(\@icons);
762             $newgroup->refresh;
763             }
764              
765             sub remove_group_icon {
766             my ($self, $matchname) = @_;
767             my $section = $self->resource_section or return;
768             my $existing = $self->get_group_icon($matchname) or return;
769             $section->remove($existing->PathName);
770             $section->refresh;
771             }
772              
773             sub _exists_group_icon {
774             my ($self, $matchname) = @_;
775             my $section = $self->resource_section or return;
776             my $langid = '#0';
777             my $res = undef;
778             for my $resource ( $section->objects('GroupIcon')) {
779             my $path = $resource->PathName;
780             my($_null, $_rtgi, $name, $_langid) = split(/\//, $path);
781             $langid = $_langid;
782             if($name eq $matchname) {
783             $res = $resource;
784             last;
785             }
786             }
787             return ( wantarray ) ? ( $res, $langid ) : $res;
788             }
789              
790             sub export_group_icon {
791             my ($self, $matchname, $filename) = @_;
792             my $existing = $self->get_group_icon($matchname) or return;
793             my $iconobject = Win32::Exe::IconFile->new();
794             my @icons = $existing->icons;
795             $iconobject->set_icons(\@icons);
796             $iconobject->write_file($filename, $iconobject->dump);
797             }
798              
799              
800             1;
801              
802             __END__