File Coverage

blib/lib/Win32/Exe/Manifest.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             #############################################################################
2             # Package Win32::Exe::Manifest
3             # Description: Handle Win32 PE Manifests
4             # Created Mon Apr 19 17:15:24 2010
5             # SVN Id $Id: Manifest.pm 2 2010-11-30 16:40:31Z mark.dootson $
6             # Copyright: Copyright (c) 2010 Mark Dootson
7             # Licence: This program is free software; you can redistribute it
8             # and/or modify it under the same terms as Perl itself
9             #############################################################################
10              
11             package Win32::Exe::Manifest;
12              
13             #############################################################################
14             # References
15             # http://msdn.microsoft.com/en-us/library/dd371711(VS.85).aspx
16             # http://msdn.microsoft.com/en-us/library/aa374191(VS.85).aspx
17             # http://msdn.microsoft.com/en-us/library/aa374219(v=VS.85).aspx
18             # http://msdn.microsoft.com/en-us/library/aa376607(v=VS.85).aspx
19             #----------------------------------------------------------------------------
20              
21 4     4   24 use strict;
  4         6  
  4         188  
22 4     4   23 use warnings;
  4         6  
  4         269  
23 4     4   3529 use Win32::Exe::Manifest::Parser;
  0            
  0            
24             use Exporter;
25             use base qw( Exporter );
26             use Carp;
27              
28             our $VERSION = '0.15';
29              
30             =head1 NAME
31              
32             Win32::Exe::Manifest - MSWin Application and Assembly manifest handling
33              
34             =head1 VERSION
35              
36             This document describes version 0.15 of Win32::Exe::Manifest, released
37             November 30, 2010.
38              
39             =head1 SYNOPSIS
40              
41             use Win32::Exe
42             my $exe = Win32:::Exe->new('somefilepath');
43            
44             my $manifest = $exe->get_manifest if $exe->has_manifest;
45            
46             # Get themed controls
47             $manifest->add_common_controls;
48            
49             # Change name
50             $manifest->set_assembly_name('My.App.Name');
51            
52             # Require Admin permissions
53             $manifest->set_execution_level('requireAdministrator');
54            
55             # write it out
56             $exe->set_manifest($manifest);
57             $exe->write;
58            
59             #roll your own
60             use Win32::Exe::Manifest
61             my $xml = $handmademanifest;
62             ......
63             my $manifest = Win32::Exe::Manifest->new($xml, 'application');
64             $exe->set_manifest($manifest);
65             $exe->write;
66            
67             #get formated $xml
68             my $xml = $manifest->output;
69            
70             #try merge (experimental)
71            
72             my $mfest1 = $exe->get_manifest;
73             my $mfest2 = Win32::Exe::Manifest->new($xml, 'application');
74            
75             $mfest1->merge_manifest($mfest2);
76            
77             $exe->set_manifest($mfest1);
78             $exe->write;
79            
80             #add a dependency
81             my $info =
82             { type => 'win32',
83             name => 'Dependency.Prog.Id',
84             version => 1.0.0.0,
85             language => '*',
86             processorArchitecture => '*',
87             publicKeyToken => 'hjdajhahdsa7sadhaskl',
88             };
89            
90             $manifest->add_dependency($info);
91             $exe->set_manifest($manifest);
92             $exe->write;
93              
94              
95              
96             =head1 DESCRIPTION
97              
98             This module parses and manipulates application and assembly
99             manifests used in MS Windows executables and DLLs. It is
100             part of the Win32::Exe distribution.
101              
102             =head1 METHODS
103              
104             =head2 Constructors
105              
106             =head3 new
107              
108             Win32::Exe::Manifest->new($xml, $type);
109              
110             Create a new manifest instance from the manifest xml in $xml. The
111             $type param is optional and may contain 'application' (the default),
112             or 'assembly'.
113              
114             Manifest objects can also be created using Win32::Exe
115              
116             my $exe = Win32:::Exe->new('somefilepath');
117             my $manifest = $exe->get_manifest if $exe->has_manifest;
118              
119             =cut
120              
121             sub new {
122             my ($class, $xml, $type) = @_;
123             my $self = bless {}, $class;
124             $xml = $self->get_default_manifest() if !$xml;
125             $type = 'application' if !$type;
126             $type =~ /^(application|assembly)$/ or croak(qq(Invalid manifest type : $type : valid types are 'application', 'assembly'));
127             $self->{_w32_exe_schema} = $self->get_default_schema;
128             $self->{_w32_exe_datatype} = $type;
129             $self->{_w32_exe_manifestid} = 1;
130             my $handler = Win32::Exe::Manifest::Parser->new( $self->get_parser_config );
131             while( $xml =~ s/""/"/g ) { carp qq(Manifest has badly formed value quotes); }
132             $self->{_w32_exe_dataref} = $handler->xml_in( $xml );
133             $self->_compress_schema();
134             my $errors = $self->validate_errors;
135             croak('Manifest XML had errors: ' . $errors) if $errors;
136             return $self;
137             }
138              
139             =head2 Output
140              
141             =head3 output
142              
143             my $xml = $manifest->output;
144              
145             Returns a formated $xml string containing all edits and changes made
146             using Win32::Exe::Manifest
147              
148              
149             =cut
150              
151             sub output {
152             my $self = shift;
153             my $handler = Win32::Exe::Manifest::Parser->new( $self->get_parser_config );
154             my $ref = $self->refhash;
155             $self->_expand_schema();
156             my $xml = $handler->xml_out( $ref );
157             $self->_compress_schema();
158             return $xml;
159             }
160              
161             =head2 Application Identity
162              
163             =head3 set_assembly_name
164              
165             $manifest->set_assembly_name('My.Application.Name');
166              
167             Set the application or assembly name. The name should take the form of a progid
168             and should not include any spaces.
169              
170              
171             =head3 get_assembly_name
172              
173             my $appname = $manifest->get_assembly_name;
174              
175             Return the assembly or application name from the manifest.
176              
177              
178             =cut
179              
180             sub set_assembly_name {
181             my($self, $name) = @_;
182             $self->refhash()->{assembly}->[0]->{assemblyIdentity}->[0]->{name} = $name;
183             my $errors = $self->validate_errors;
184             croak('Manifest XML had errors following set name: ' . $errors) if $errors;
185             }
186              
187             sub get_assembly_name {
188             my($self) = @_;
189             my $ref = $self->refhash();
190             return (exists($ref->{assembly}->[0]->{assemblyIdentity}->[0]->{name})) ? $ref->{assembly}->[0]->{assemblyIdentity}->[0]->{name} : undef;
191             }
192              
193             =head3 set_assembly_description
194              
195             $manifest->set_assembly_description('My Application Description');
196              
197             Set the application description. The description is an informative string.
198              
199              
200             =head3 get_assembly_decription
201              
202             my $desc = $manifest->get_assembly_description;
203              
204             Return the assembly description from the manifest.
205              
206              
207             =cut
208              
209             sub set_assembly_description {
210             my($self, $desc) = @_;
211             my $valuename = $self->default_content;
212             $self->refhash()->{assembly}->[0]->{description}->[0]->{$valuename} = $desc;
213             my $errors = $self->validate_errors;
214             croak('Manifest XML had errors following set description: ' . $errors) if $errors;
215             }
216              
217             sub get_assembly_description {
218             my($self) = @_;
219             my $valuename = $self->default_content;
220             my $ref = $self->refhash();
221             return (exists($ref->{assembly}->[0]->{description}->[0]->{$valuename})) ? $ref->{assembly}->[0]->{description}->[0]->{$valuename} : undef;
222             }
223              
224             =head3 set_assembly_version
225              
226             $manifest->set_assembly_version('1.7.8.34456');
227              
228             Set the application or assembly version. The version should take the form of
229             'n.n.n.n' where each n is a number between 0-65535 inclusive.
230              
231              
232             =head3 get_assembly_version
233              
234             my $version = $manifest->get_assembly_version;
235              
236             Return the assembly or application version from the manifest.
237              
238              
239             =cut
240              
241             sub set_assembly_version {
242             my($self, $version) = @_;
243             $self->refhash()->{assembly}->[0]->{assemblyIdentity}->[0]->{version} = $version;
244             my $errors = $self->validate_errors;
245             croak('Manifest XML had errors following set version: ' . $errors) if $errors;
246             }
247              
248             sub get_assembly_version {
249             my($self) = @_;
250             my $ref = $self->refhash();
251             return (exists($ref->{assembly}->[0]->{assemblyIdentity}->[0]->{version})) ? $ref->{assembly}->[0]->{assemblyIdentity}->[0]->{version} : undef;
252             }
253              
254             =head3 set_assembly_language
255              
256             $manifest->set_assembly_language($langid);
257              
258             Set the application or assembly language. The language id is the
259             DHTML language code. If you want to set 'language neutral' then pass
260             '*' for the value.
261              
262             see : L
263              
264              
265             =head3 get_assembly_language
266              
267             my $langid = $manifest->get_assembly_language;
268              
269             Return the assembly or application language from the manifest. If there
270             is no language id in the manifest, the method will return '*'
271              
272              
273             =cut
274              
275             sub set_assembly_language {
276             my($self, $newval) = @_;
277             $self->refhash()->{assembly}->[0]->{assemblyIdentity}->[0]->{language} = $newval;
278             my $errors = $self->validate_errors;
279             croak('Manifest XML had errors following set language: ' . $errors) if $errors;
280             }
281              
282             sub get_assembly_language {
283             my($self) = @_;
284             my $ref = $self->refhash();
285             return (exists($ref->{assembly}->[0]->{assemblyIdentity}->[0]->{language})) ? $ref->{assembly}->[0]->{assemblyIdentity}->[0]->{language} : '*';
286             }
287              
288             =head3 set_assembly_architecture
289              
290             $manifest->set_assembly_architecture($arch);
291              
292             Set the application or assembly architecture. Accepted values are :
293             x86 msil ia64 amd64 *. Note the lowercase format. If you want your manifest
294             to be architecture neutral, set architecture to '*'.
295              
296              
297             =head3 get_assembly_architecture
298              
299             my $arch = $manifest->get_assembly_architecture;
300              
301             Return the assembly or application architecture from the manifest.
302              
303              
304             =cut
305              
306             sub set_assembly_architecture {
307             my($self, $newval) = @_;
308             $self->refhash()->{assembly}->[0]->{assemblyIdentity}->[0]->{processorArchitecture} = $newval;
309             my $errors = $self->validate_errors;
310             croak('Manifest XML had errors following set processorArchitecture: ' . $errors) if $errors;
311             }
312              
313             sub get_assembly_architecture {
314             my($self) = @_;
315             my $ref = $self->refhash();
316             return (exists($ref->{assembly}->[0]->{assemblyIdentity}->[0]->{processorArchitecture})) ? $ref->{assembly}->[0]->{assemblyIdentity}->[0]->{processorArchitecture} : '*';
317             }
318              
319             =head2 Trust and Security
320              
321             =head3 set_execution_level
322              
323             $manifest->set_execution_level($level);
324              
325             Set the application execution level. Accepted values are : asInvoker,
326             highestAvailable, requireAdministrator, none. If you pass the value
327             'none', any trustInfo section will be removed from the manifest.
328              
329             See L
330              
331              
332             =head3 get_execution_level
333              
334             my $level = $manifest->get_execution_level;
335              
336             Return the application execution level.
337              
338              
339             =cut
340              
341             sub set_execution_level {
342             my($self, $level) = @_;
343             $level =~ /^(asInvoker|requireAdministrator|highestAvailable|none)$/
344             or croak(qq(Invalid exec level '$level'. Valid levels are 'asInvoker', 'requireAdministrator', 'highestAvailable', 'none'));
345            
346             my $ref = $self->refhash()->{assembly}->[0];
347             my $schema = $self->get_current_schema;
348             my $xmlns = $schema->{elementtypes}->{trustInfo}->{attributes}->{xmlns}->{default};
349            
350             if($level eq 'none') {
351             # delete element and collapse tree if empty
352             $self->_delete_collapse_tree($ref, [ qw(trustInfo security requestedPrivileges requestedExecutionLevel) ] );
353             } else {
354             # create value if it does not exist
355             my $writeref = $self->_get_first_tree_node($ref, [ qw(trustInfo security requestedPrivileges requestedExecutionLevel) ], undef );
356             $writeref->{level} = $level;
357             $writeref->{uiAccess} = 'false' if not exists($writeref->{uiAccess});
358             }
359            
360             # in all cases, dump any namespace definitions - which cause crashes on some OS / namespace combos
361             # and fix trustInfo namespace decl
362            
363             $self->_fixup_namespace( $ref, 'trustInfo', $xmlns );
364            
365             my $errors = $self->validate_errors;
366             croak('Manifest XML had errors following set execution level: ' . $errors) if $errors;
367             }
368              
369             sub get_execution_level {
370             my $self = shift;
371             my $ref = $self->refhash()->{assembly}->[0];
372             return 'none' if not exists($ref->{trustInfo}->[0]->{security}->[0]->{requestedPrivileges}->[0]->{requestedExecutionLevel}->[0]->{level});
373             return $ref->{trustInfo}->[0]->{security}->[0]->{requestedPrivileges}->[0]->{requestedExecutionLevel}->[0]->{level};
374             }
375              
376             =head3 set_uiaccess
377              
378             $manifest->set_uiaccess($needed);
379              
380             Set the application uiAccess requirement in the trustInfo manifest section.
381             Accepted values are 'true', 'false'. If no trustInfo section exists, one is
382             created with the execution level set to 'asInvoker'.
383              
384             See L
385              
386              
387             =head3 get_uiaccess
388              
389             my $accessneeded = $manifest->get_uiaccess;
390              
391             Return the uiAccess setting from the trustInfo structure. If no trustInfo
392             structure exists, method returns undef.
393              
394              
395             =cut
396              
397             sub set_uiaccess {
398             my ($self, $access) = @_;
399             $access =~ /^(true|false)$/
400             or croak(qq(Invalid uiAccess setting '$access'. Valid settings are 'true', 'false'));
401            
402             my $ref = $self->refhash()->{assembly}->[0];
403             if(exists($ref->{trustInfo}->[0]->{security}->[0]->{requestedPrivileges}->[0]->{requestedExecutionLevel}->[0]->{uiAccess})) {
404             $ref->{trustInfo}->[0]->{security}->[0]->{requestedPrivileges}->[0]->{requestedExecutionLevel}->[0]->{uiAccess} = $access;
405             } else {
406             my $schema = $self->get_current_schema;
407             my $xmlns = $schema->{elementtypes}->{trustInfo}->{attributes}->{xmlns}->{default};
408             $ref->{trustInfo} = [ { xmlns => $xmlns, security => [ { requestedPrivileges => [ { requestedExecutionLevel => [ { level => 'asInvoker', uiAccess => $access } ] } ] } ] } ];
409             }
410             my $errors = $self->validate_errors;
411             croak('Manifest XML had errors following set uiAccess: ' . $errors) if $errors;
412             }
413              
414             sub get_uiaccess {
415             my $self = shift;
416             my $ref = $self->refhash()->{assembly}->[0];
417             return undef if not exists($ref->{trustInfo}->[0]->{security}->[0]->{requestedPrivileges}->[0]->{requestedExecutionLevel});
418             return $ref->{trustInfo}->[0]->{security}->[0]->{requestedPrivileges}->[0]->{requestedExecutionLevel}->[0]->{uiAccess};
419             }
420              
421             =head2 Application Dependencies
422              
423             =head3 set_resource_id
424              
425             $manifest->set_resource_id($id);
426              
427             Set the resource Id for the manifest. Valid id's are 1, 2 and 3. The default
428             is 1. Don't set this unless you are fully aware of the effects.
429              
430             See L
431              
432              
433             =head3 get_resource_id
434              
435             my $id = $manifest->get_resource_id();
436              
437             Return the resource Id for the manifest.
438              
439             =cut
440              
441             sub get_resource_id { $_[0]->{_w32_exe_manifestid} }
442              
443             sub set_resource_id {
444             my ($self, $id) = @_;
445             $id =~ /^(1|2|3)$/ or croak(qq(invalid manifest resource id $id. Valid values are 1,2,3));
446             $self->{_w32_exe_manifestid} = $id;
447             }
448              
449             =head3 add_common_controls
450              
451             $manifest->add_common_controls();
452              
453             Add a dependency on minimum version 6.0.0.0 of the Microsoft.Windows.Common-Controls shared
454             library. This is normally done with GUI applications to use themed controls on Windows XP
455             and above.
456              
457              
458             =cut
459              
460             sub add_common_controls { $_[0]->add_template_dependency('Microsoft.Windows.Common-Controls'); }
461              
462             sub add_template_dependency {
463             my($self, $assemblyname) = @_;
464             my $tmpl = $self->get_dependency_template($assemblyname);
465             croak(qq(No template found for dependency $assemblyname)) if !$tmpl;
466             $self->add_dependency($tmpl);
467             }
468              
469             =head3 add_dependency
470              
471             $manifest->add_dependency($info);
472              
473             Add a dependency on the assembly detailed in the $info hash reference. The contents of
474             $info should be of the form:
475              
476             my $info = { type => 'win32',
477             name => 'Dependency.Prog.Id',
478             version => 1.0.0.0,
479             language => '*',
480             processorArchitecture => '*',
481             publicKeyToken => 'hjdajhahdsa7sadhaskl',
482             };
483              
484             Note that the version should be the least specific that your application requires. For
485             example, a version of '2.0.0.0' would mean the system loads the first matching
486             assembly it finds with a version of at least '2.0.0.0'.
487              
488             See: L
489              
490              
491             =cut
492              
493             sub add_dependency {
494             my($self, $proto) = @_;
495             my $name = $proto->{name};
496             $proto->{type} = 'win32';
497             $proto->{version} ||= '0.0.0.0';
498             $proto->{language} ||= '*';
499             $proto->{processorArchitecture} ||= '*';
500             my %vals = %$proto;
501             if( defined(my $depindex = $self->_get_dependendency_index($name)) ) {
502             $self->refhash()->{assembly}->[0]->{dependency}->[$depindex]->{dependentAssembly}->[0]->{assemblyIdentity}->[0] = \%vals;
503             } else {
504             $self->refhash()->{assembly}->[0]->{dependency} = [] if not exists $self->refhash()->{assembly}->[0]->{dependency};
505             my $newdep = { dependentAssembly => [ { assemblyIdentity => [ \%vals ] } ] };
506             push @{ $self->refhash()->{assembly}->[0]->{dependency} } , $newdep;
507             }
508             my $errors = $self->validate_errors;
509             croak('Manifest XML had errors following dependency addition: ' . $errors) if $errors;
510             }
511              
512             =head3 remove_dependency
513              
514             $manifest->remove_dependency($progid);
515              
516             Remove a dependency with the $progid. For example, passing a $progid of
517             'Microsoft.Windows.Common-Controls' will remove the dependency added via
518             'add_common_controls' from the manifest.
519              
520              
521             =cut
522              
523             sub remove_dependency {
524             my($self, $depname) = @_;
525             if( defined(my $depindex = $self->_get_dependendency_index($depname)) ) {
526             my $ref = $self->refhash()->{assembly}->[0]->{dependency};
527             my @depends = @$ref;
528             splice(@depends, $depindex, 1);
529             $self->refhash()->{assembly}->[0]->{dependency} = \@depends;
530             return 1;
531             } else {
532             return 0;
533             }
534             }
535              
536             =head3 get_dependency
537              
538             my $info = $manifest->get_dependency($progid);
539              
540             Return a dependency info hash for a dependency in the manifest with the 'name'
541             $progid. The info hash is a reference to a hash with the format:
542              
543             { type => 'win32',
544             name => 'Dependency.Prog.Id',
545             version => 1.0.0.0,
546             language => '*',
547             processorArchitecture => '*',
548             publicKeyToken => 'hjdajhahdsa7sadhaskl',
549             };
550              
551             If there is no dependency with the name $progid, returns undef.
552              
553              
554             =cut
555              
556             sub get_dependency {
557             my($self, $depname) = @_;
558             if( defined(my $depindex = $self->_get_dependendency_index($depname)) ) {
559             my $ref = $self->refhash()->{assembly}->[0]->{dependency}->[$depindex]->{dependentAssembly}->[0]->{assemblyIdentity}->[0];
560             my %vals = %$ref;
561             return \%vals;
562             } else {
563             return undef;
564             }
565             }
566              
567              
568             =head3 get_dependencies
569              
570             my @deps = $manifest->get_dependencies($progid);
571              
572             Return an array of hash references, one for each dependency in the manifest.
573             Each member is a reference to a hash with the format:
574              
575             { type => 'win32',
576             name => 'Dependency.Prog.Id',
577             version => 1.0.0.0,
578             language => '*',
579             processorArchitecture => '*',
580             publicKeyToken => 'hjdajhahdsa7sadhaskl',
581             };
582              
583             If there are no dependencies, returns an empty array.
584              
585              
586             =cut
587              
588             sub get_dependencies {
589             my $self = shift;
590             my @depends = ();
591             return (@depends) if not exists $self->refhash()->{assembly}->[0]->{dependency};
592             for my $dependency ( @{ $self->refhash()->{assembly}->[0]->{dependency} } ) {
593             my $dep = $dependency->{dependentAssembly}->[0]->{assemblyIdentity}->[0];
594             my %vals = %$dep;
595             push(@depends, \%vals);
596             }
597             return ( @depends );
598             }
599              
600              
601             =head2 Compatibility Settings
602              
603              
604             =head3 set_compatibility
605              
606             $manifest->set_compatibility( ('Windows Vista') );
607              
608             Set the operating system feature compatibility flags. Parameter is a list of operating
609             systems that the application targets. In addition to the opertating system identifier
610             keys, this method also accepts the shorthand strings 'Windows Vista' and 'Windows 7'.
611              
612             See : L
613              
614              
615             =head3 get_compatibility
616              
617             my @osids = $manifest->get_compatibility();
618              
619             Returns a list of operating system identifier keys that the manifest notes as targetted
620             operating systems. You can convert these os ids to the shorthand strings 'Windows Vista'
621             and 'Windows 7' using the method
622              
623             my $shortstring = $manifest->get_osname_from_osid($osid);
624              
625             There is a reverse method
626              
627             my $osid = $manifest->get_osid_from_osname($shortstring);
628              
629             NOTE: Don't set this unless you fully understand the effects.
630              
631             See : L
632              
633              
634             =cut
635              
636             sub set_compatibility {
637             my($self, @vals) = @_;
638             return if ! scalar(@vals);
639             my @osblock = ();
640             for my $val ( @vals ) {
641             my $compat = $val;
642             $compat = '{e2011457-1546-43c5-a5fe-008deee3d3f0}' if(lc($compat) eq 'windows vista');
643             $compat = '{35138b9a-5d96-4fbd-8e2d-a2440225f93a}' if(lc($compat) eq 'windows 7');
644             croak(qq(Invalid OS key '$val' for compatibility)) if !Win32::Exe::Manifest::Parser->validate_osid($compat);
645             push(@osblock, { Id => $compat });
646             }
647             my $ref = $self->refhash()->{assembly}->[0];
648             my $schema = $self->get_current_schema;
649             my $xmlns = $schema->{elementtypes}->{compatibility}->{attributes}->{xmlns}->{default};
650             $ref->{compatibility} = [ { xmlns => $xmlns, application => [ { supportedOS => \@osblock, }, ], }, ];
651             my $errors = $self->validate_errors;
652             croak('Manifest XML had errors following set compatibility: ' . $errors) if $errors;
653             }
654              
655             sub get_compatibility {
656             my $self = shift;
657             my @compats = ();
658             my $ref = $self->refhash()->{assembly}->[0];
659             if(exists($ref->{compatibility})) {
660             my @oids = @{ $ref->{compatibility}->[0]->{application}->[0]->{supportedOS} };
661             for my $oidref ( @oids ) {
662             my $oskey = $oidref->{Id};
663             push(@compats, $oskey);
664             }
665             }
666             return (@compats);
667             }
668              
669             =head3 set_dpiaware
670              
671             $manifest->set_dpdaware( 'true' );
672              
673             Set section in the manifest if the application is dpi aware. Accepts values
674             true, false, and none. If the value 'none' is passed, the application\windowsSettings
675             section is removed from the manifest entirely.
676              
677             See : L
678              
679              
680             =head3 get_dpiaware
681              
682             $manifest->set_dpdaware( 'true' );
683              
684             Return the dpiAware setting from the manifest, if any. If there is no setting,
685             the method returns undef.
686              
687             See : L
688              
689              
690             =cut
691              
692             sub set_dpiaware {
693             my ($self, $setval) = @_;
694            
695             $setval =~ /^(true|false|none)$/i or croak(qq(Invalid value for set_dpiaware '$setval'. Valid values are true:false:none));
696             $setval = lc($setval);
697             my $ref = $self->refhash()->{assembly}->[0];
698             my $elementvaluename = $self->default_content;
699             my $schema = $self->get_current_schema;
700             my $namealias = $schema->{namespace}->{'urn:schemas-microsoft-com:asm.v3'};
701            
702             # handle remove value
703             if($setval eq 'none') {
704             # delete element and collapse tree if empty
705             $self->_delete_collapse_tree($ref, [ qw(application windowSettings dpiAware) ] );
706             } else {
707             # create value if it does not exist
708             my $writeref = $self->_get_first_tree_node($ref, [ qw(application windowsSettings) ], $namealias );
709             $writeref->{dpiAware} = [ { $elementvaluename => $setval } ];
710             # add xmlns for windowsSettings
711             my $xmlns = $schema->{elementtypes}->{windowsSettings}->{attributes}->{xmlns}->{default};
712             $ref->{application}->[0]->{windowsSettings}->[0]->{xmlns} = $xmlns;
713             }
714             my $errors = $self->validate_errors;
715             croak('Manifest XML had errors after dpiAware setting: ' . $errors) if $errors;
716             }
717              
718             sub get_dpiaware {
719             my $self = shift;
720             my $ref = $self->refhash()->{assembly}->[0];
721             my $elementvaluename = $self->default_content;
722             return 'none' if(not exists($ref->{application}->[0]->{windowsSettings}->[0]->{dpiAware}->[0]->{$elementvaluename}));
723             return $ref->{application}->[0]->{windowsSettings}->[0]->{dpiAware}->[0]->{$elementvaluename};
724             }
725              
726             =head2 Manifest Information
727              
728             =head3 get_manifest_type
729              
730             my $type = $manifest->get_manifest_type;
731              
732             Returns the manifest type ( 'application' or 'assembly' );
733              
734              
735             =cut
736              
737             sub get_manifest_type { $_[0]->{_w32_exe_datatype}; }
738              
739              
740             sub get_current_schema { $_[0]->{_w32_exe_schema}; }
741              
742              
743             sub set_current_schema {
744             my($self, $schema) = @_;
745             $self->{_w32_exe_schema} = $schema;
746             my $errors = $self->validate_errors;
747             croak('Manifest XML had errors following set schema: ' . $errors) if $errors;
748             }
749              
750             sub default_xmldecl { '' }
751              
752             sub default_content { 'elementValue' }
753              
754             sub get_parser_config {
755             my $self = shift;
756             my %config = (
757             KeepRoot => 1,
758             ForceArray => 1,
759             KeyAttr => {},
760             XMLDecl => $self->default_xmldecl,
761             ForceContent => 1,
762             ContentKey => $self->default_content,
763             # AttrIndent => 1,
764             );
765             return %config;
766             }
767              
768             sub refhash { $_[0]->{_w32_exe_dataref}; }
769              
770              
771             sub merge_manifest {
772             my($self, $xml) = @_;
773             my $mergefest;
774             my $class = ref($self);
775             eval { $mergefest = $class->new($xml, $self->get_manifest_type); };
776             croak(qq(Merging $@)) if $@;
777             my $newref = $mergefest->refhash()->{assembly}->[0];
778             my $oldref = $self->refhash()->{assembly}->[0];
779             $self->_merge_element_merge('assembly', $oldref, $newref);
780             my $errors = $self->validate_errors;
781             croak('Manifest had errors after merging: ' . $errors) if $errors;
782             }
783              
784              
785             sub get_osname_from_osid {
786             my($self, $osid) = @_;
787             my $name = 'Unknown Windows Version';
788             $name = 'Windows Vista' if $osid =~ /e2011457-1546-43c5-a5fe-008deee3d3f0/;
789             $name = 'Windows 7' if $osid =~ /35138b9a-5d96-4fbd-8e2d-a2440225f93a/;
790             return $name;
791             }
792              
793             sub get_osid_from_osname {
794             my($self, $name) = @_;
795             my $osid = undef;
796             $osid = '{e2011457-1546-43c5-a5fe-008deee3d3f0}' if(lc($name) eq 'windows vista');
797             $osid = '{35138b9a-5d96-4fbd-8e2d-a2440225f93a}' if(lc($name) eq 'windows 7');
798             return $osid;
799             }
800              
801             sub _get_dependendency_index {
802             my($self, $name) = @_;
803             my $exref = $self->refhash()->{assembly}->[0];
804             return undef if not exists $exref->{dependency};
805             my $rval = undef;
806             my @existing = @{ $exref->{dependency} };
807             for(my $i = 0; $i < @existing; $i++) {
808             my $depname = $existing[$i]->{dependentAssembly}->[0]->{assemblyIdentity}->[0]->{name};
809             if($depname eq $name) {
810             $rval = $i;
811             last;
812             }
813             }
814             return $rval;
815             }
816              
817             sub _get_first_tree_node {
818             my ($self, $ref, $paths, $namealias ) = @_;
819             my $newref = $ref;
820             while(my $path = shift(@$paths) ) {
821             $newref->{$path} = [{}] if(not exists($newref->{$path}));
822             $newref = $newref->{$path}->[0];
823             $newref->{elementnamespace} = $namealias if $namealias;
824             }
825             return $newref;
826             }
827              
828             sub _delete_collapse_tree {
829             my ($self, $ref, $paths ) = @_;
830             my $rootkey = $paths->[0];
831             my $deletekey = pop(@$paths);
832             my $delref = $ref;
833             my $dodelete = 1;
834             while(my $checkpath = shift @$paths) {
835             if(exists($delref->{$checkpath}->[0])) {
836             $delref = $delref->{$checkpath}->[0];
837             } else {
838             $dodelete = 0;
839             last;
840             }
841             }
842             delete($delref->{$deletekey}) if($dodelete && exists($delref->{$deletekey}));
843             delete($delref->{elementnamespace}) if($dodelete && exists($delref->{elementnamespace}));
844             $self->_delete_if_empty($ref, $rootkey);
845             }
846              
847             sub _merge_element_merge {
848             my($self, $elementname, $exref, $mergeref) = @_;
849             my $schema = $self->get_current_schema;
850             my $elementvaluename = $self->default_content;
851             my $elementdef = $schema->{elementtypes}->{$elementname};
852            
853             my @keynames = (sort keys(%$mergeref));
854             my %elementnames = ();
855             my %attributenames = ();
856             my $valuepresent = 0;
857            
858             for my $kname(@keynames) {
859             if( $kname eq $elementvaluename ) {
860             $valuepresent = 1;
861             } elsif(ref($mergeref->{$kname})) {
862             $elementnames{$kname} = 1;
863             } else {
864             $attributenames{$kname} = 1;
865             }
866             }
867            
868             # merge value
869             if($valuepresent) {
870             $exref->{$elementvaluename} = $mergeref->{$elementvaluename};
871             }
872            
873             # merge attributes
874             for my $aname (sort keys( %attributenames )) {
875             $exref->{$aname} = $mergeref->{$aname};
876             }
877            
878             # merge elements
879             for my $ename (sort keys( %elementnames )) {
880             if(not exists($exref->{$ename})) {
881             # simple addition
882             $exref->{$ename} = $mergeref->{$ename};
883             next;
884             }
885             if($ename eq 'dependency') {
886             # handle dependency merge
887             $self->_merge_dependencies( $exref, $mergeref );
888             next;
889             }
890             my $maxallowed = $elementdef->{elements}->{$ename}->{max};
891             if($maxallowed == 1) {
892             $self->_merge_element_merge($ename, $exref->{$ename}->[0], $mergeref->{$ename}->[0]);
893             } else {
894             push(@{ $exref->{$ename} }, @{ $mergeref->{$ename} });
895             }
896             }
897             }
898              
899             sub _merge_dependencies {
900             my($self, $exref, $mergeref) = @_;
901             my %existingdepends = ();
902             my %mergingdepends = ();
903            
904             my @existing = @{ $exref->{dependency} };
905             my @merging = @{ $mergeref->{dependency} };
906             my @merged = ();
907            
908             for(my $i = 0; $i < @existing; $i++) {
909             my $depname = $existing[$i]->{dependentAssembly}->[0]->{assemblyIdentity}->[0]->{name};
910             $existingdepends{$depname} = $i;
911             }
912            
913             for(my $i = 0; $i < @merging; $i++) {
914             my $depname = $merging[$i]->{dependentAssembly}->[0]->{assemblyIdentity}->[0]->{name};
915             $mergingdepends{$depname} = $i;
916             }
917            
918             foreach my $classname (sort keys(%existingdepends)) {
919             push(@merged, $existing[$existingdepends{$classname}]) if not exists $mergingdepends{$classname}
920             }
921            
922             push(@merged, @merging);
923             $exref->{dependency} = \@merged;
924             }
925              
926             sub validate_errors {
927             my $self = shift;
928             eval { $self->validate_data; };
929             return ($@) ? $@ : undef;
930             }
931              
932             sub _fixup_namespace {
933             my( $self, $ref, $keyname, $xmlns ) = @_;
934             for my $element( @{ $ref->{$keyname} } ) {
935             $element->{xmlns} = $xmlns if $xmlns;
936             delete($element->{elementnamespace}) if(exists($element->{elementnamespace}));
937             my @keynames = (sort keys(%$element));
938             for my $subkeyname( @keynames ) {
939             if(ref($element->{$subkeyname})) {
940             # a sub-element
941             $self->_fixup_namespace($element, $subkeyname, undef );
942             }
943             }
944             }
945             }
946              
947             sub _delete_if_empty {
948             my($self, $ref, $refname) = @_;
949            
950             # this element array is empty if it only contains namespace attributes
951             # and all it's elements are similarly empty;
952            
953             my $candelete = 1;
954             for my $element ( @{ $ref->{$refname} }) {
955             my @keynames = (sort keys(%$element));
956             for my $keyname( @keynames ) {
957             next if $keyname eq 'elementnamespace';
958             next if $keyname =~ /^xmlns/;
959             my $value = $element->{$keyname};
960             if(ref($value)) {
961             # this is an element array
962             $candelete = 0 if !$self->_delete_if_empty($element, $keyname);
963             } else {
964             # we have a none-namespace attribute
965             $candelete = 0;
966             }
967             }
968             }
969              
970             delete($ref->{$refname}) if $candelete;
971             return $candelete;
972             }
973              
974             sub _compress_schema {
975             my $self = shift;
976             my $ref = $self->refhash;
977             $self->_compress_element_reference($ref);
978             }
979              
980             sub _compress_element_reference {
981             my($self, $hashref) = @_;
982             my $schema = $self->get_current_schema;
983            
984             my @attributes;
985             my @elements;
986            
987             for my $keyname ( sort keys(%$hashref) ) {
988             my $value = $hashref->{$keyname};
989             if(ref($value)) {
990             push(@elements, $keyname);
991             } else {
992             push(@attributes, $keyname);
993             }
994             }
995            
996             for my $keyname ( @attributes ) {
997             #next if $keyname eq 'elementnamespace'; # - should never appear
998             my $value = $hashref->{$keyname};
999             if($keyname =~ /^xmlns:(.+)$/) {
1000             # this is a namespace declaration
1001             # record it in the current schema
1002             # and delete it from the record
1003             my $replacerequired = 0;
1004             my $namespacesname = $1;
1005             if(exists($schema->{namespace}->{$value})) {
1006             # namespace exists
1007             my $alias = $schema->{namespace}->{$value};
1008             if($namespacesname ne $alias) {
1009             $schema->{nstranslation}->{$namespacesname} = $alias;
1010             $namespacesname = $alias;
1011             $replacerequired = 1;
1012             }
1013             } else {
1014             # make sure namespace name is unique before adding values
1015             while(exists($schema->{nstranslation}->{$namespacesname})) {
1016             $namespacesname .= 'n';
1017             }
1018             $schema->{namespace}->{$value} = $namespacesname;
1019             $schema->{nstranslation}->{$namespacesname} = $namespacesname;
1020             $replacerequired = 1;
1021             }
1022             # delete the declaration
1023             delete($hashref->{$keyname});
1024             }
1025             }
1026            
1027             for my $keyname ( @elements ) {
1028             my $value = $hashref->{$keyname};
1029             if($keyname =~ /^(.+):(.+)$/) {
1030             # element has a namespace
1031             my $namespace = $1;
1032             my $shortname = $2;
1033             my $nsalias = $schema->{nstranslation}->{$namespace} || $namespace;
1034             $hashref->{$shortname} = $value;
1035             delete($hashref->{$keyname});
1036            
1037             $_->{elementnamespace} = $nsalias for( @$value );
1038            
1039             }
1040             $self->_compress_element_reference($_) for ( @$value );
1041             }
1042             }
1043              
1044             sub _expand_schema {
1045             my $self = shift;
1046             $self->{parser}->{namespaces} = {};
1047             my $ref = $self->refhash;
1048             $self->_expand_element_reference($ref);
1049             # add namespace keys to
1050             my $assembly = $ref->{assembly}->[0];
1051             if($assembly) {
1052             foreach my $key (sort keys(%{ $self->{parser}->{namespaces} })) {
1053             my $xmlns = $self->{parser}->{namespaces}->{$key};
1054             my $xmlnsname = qq(xmlns:$key);
1055             $assembly->{$xmlnsname} = $xmlns;
1056             }
1057             }
1058             $self->{parser}->{namespaces} = {};
1059             }
1060              
1061             sub _expand_element_reference {
1062             # prepend saved namespace prefixes to elements
1063             my($self, $hashref) = @_;
1064             my $schema = $self->get_current_schema;
1065             my @keynames = ( sort keys(%$hashref) );
1066             for my $keyname ( @keynames ) {
1067             my $value = $hashref->{$keyname};
1068             if(ref($value)) { # an array
1069             # get namespace if any
1070             my $namespace = undef;
1071             for my $element(@$value) {
1072             if(exists($element->{elementnamespace})) {
1073             $namespace = $element->{elementnamespace};
1074             delete($element->{elementnamespace});
1075             }
1076             }
1077             if($namespace) {
1078             my $newname = qq($namespace:$keyname);
1079             $hashref->{$newname} = $value;
1080             delete($hashref->{$keyname});
1081             my $xmlns = $schema->{namespacelookup}->{$namespace};
1082             $self->{parser}->{namespaces}->{$namespace} = $xmlns;
1083             }
1084             $self->_expand_element_reference($_) for ( @$value );
1085             }
1086             }
1087             }
1088              
1089             sub validate_data {
1090             my($self) = @_;
1091             # top level element must be assembly
1092             # grab that and validate onwards
1093            
1094             my $schema = $self->get_current_schema;
1095             my $ref = $self->refhash;
1096             my @levels = sort keys(%$ref);
1097             croak('Too many top level elements : ' . join(', ', @levels)) if (scalar(@levels) != 1);
1098             croak(qq(Unexpected top level element $levels[0])) if $levels[0] ne 'assembly';
1099            
1100             my $elementname = 'assembly';
1101             my $elementdef = exists($schema->{elementtypes}->{$elementname}) ? $schema->{elementtypes}->{$elementname} : undef;
1102             croak(qq(no definition found for element type $elementname)) if !defined($elementdef);
1103             my $min = 1;
1104             my $max = 1;
1105             my $raw = $ref->{assembly};
1106             $self->validate_element_array('assembly', $min, $max, $raw);
1107             }
1108              
1109             sub validate_element_array {
1110             my($self, $elementname, $min, $max, $ref) = @_;
1111            
1112             my $schema = $self->get_current_schema;
1113             my $elementvaluename = $self->default_content;
1114            
1115             # get element definition
1116             my $elementdef = exists($schema->{elementtypes}->{$elementname}) ? $schema->{elementtypes}->{$elementname} : undef;
1117             croak(qq(no definition found for element type $elementname)) if !defined($elementdef);
1118            
1119             # check if this manifest type accepts element type
1120             my $currenttype = $self->get_manifest_type;
1121             my $exclusive = $elementdef->{exclusive};
1122             if(($exclusive ne 'none') && ($currenttype ne $exclusive)) {
1123             croak(qq(element type $elementname cannot appear in $currenttype manifests));
1124             }
1125            
1126             # check that numbers are OK
1127             my $numelements = scalar @$ref;
1128             croak (qq(not enough $elementname elements - count = $numelements - minimum = $min)) if(($min > 0) && ($numelements < $min));
1129             croak (qq(too many $elementname elements - count = $numelements - maximum = $max)) if(($max > 0) && ($numelements > $max));
1130            
1131             # validate each element item
1132             $self->validate_element($elementname, $elementdef, $_) for (@$ref);
1133             }
1134              
1135             sub validate_element {
1136             my($self, $ename, $elementdef, $ref) = @_;
1137            
1138             # ref contains elements, and attributes
1139             # any value is present as attribute 'elementvalue'
1140             # elements are arrayrefs
1141             # attributes are values
1142            
1143             my $schema = $self->get_current_schema;
1144             my $elementvaluename = $self->default_content;
1145            
1146             my @keynames = (sort keys(%$ref));
1147             my %elementnames = ();
1148             my %attributenames = ();
1149             my $valuepresent = 0;
1150            
1151             for my $kname(@keynames) {
1152             next if $kname eq 'elementnamespace';
1153             if( $kname eq $elementvaluename ) {
1154             $valuepresent = 1;
1155             } elsif(ref($ref->{$kname})) {
1156             if(not exists($elementdef->{elements}->{$kname})) {
1157             croak(qq(Unexpected element $kname in $ename));
1158             }
1159             $elementnames{$kname} = 1;
1160             } else {
1161             # allow no value namespace attributes to pass
1162             next if $kname =~ /^xmlns:.+$/;
1163             next if $kname eq 'xmlns';
1164             if(not exists($elementdef->{attributes}->{$kname})) {
1165             croak(qq(Unexpected attribute $kname in $ename));
1166             }
1167             $attributenames{$kname} = 1;
1168             }
1169             }
1170            
1171             # check we have all required elements
1172             foreach my $ekey (sort keys(%{ $elementdef->{elements} })) {
1173             if( $elementdef->{elements}->{$ekey}->{min} > 0 ) {
1174             croak qq(required element $ekey not found) if !exists($elementnames{$ekey});
1175             }
1176             }
1177             # check we have all required attributes
1178             foreach my $akey (sort keys(%{ $elementdef->{attributes} })) {
1179             next if $akey eq 'xmlns';
1180             if( $elementdef->{attributes}->{$akey}->{required} == 1 ) {
1181             croak qq(required attribute $akey not found) if !exists($attributenames{$akey});
1182             }
1183             }
1184             # check we have correct value / novalue
1185             my $valuerequired = $elementdef->{content}->{value};
1186             if($valuerequired != $valuepresent) {
1187             croak qq( unexpected value in $ename - value required = $valuerequired : value present = $valuepresent);
1188             }
1189            
1190             # validate any value
1191             if($valuerequired) {
1192             my $validator = $elementdef->{value_validator};
1193             Win32::Exe::Manifest::Parser->$validator($ref->{$elementvaluename})
1194             or croak(qq(Invalid Value $ref->{$elementvaluename} for element $ename));
1195             }
1196            
1197             # validate attributes
1198             foreach my $aname (sort keys( %attributenames ) ) {
1199             next if $aname eq 'xmlns';
1200             my $avalue = $ref->{$aname};
1201             my $validator = $schema->{attributes}->{$aname};
1202             Win32::Exe::Manifest::Parser->$validator($avalue) or croak(qq(Invalid value $avalue for attribute $aname in $ename));
1203             }
1204            
1205             # validate elements
1206             foreach my $sub_ename (sort keys( %elementnames ) ) {
1207             my $elearryref = $ref->{$sub_ename};
1208             my $min = $elementdef->{elements}->{$sub_ename}->{min};
1209             my $max = $elementdef->{elements}->{$sub_ename}->{max};
1210             $self->validate_element_array($sub_ename, $min, $max, $elearryref);
1211             }
1212             }
1213              
1214             sub get_dependency_template {
1215             my ($self, $name) = @_;
1216             my $dependencytemplates = $self->get_dependency_template_hash;
1217             if(exists($dependencytemplates->{$name})) {
1218             return $dependencytemplates->{$name};
1219             } else {
1220             return undef;
1221             }
1222             }
1223              
1224             sub get_dependency_template_hash {
1225             my $self = shift;
1226             my $dependencytemplates = {
1227             'Microsoft.Windows.Common-Controls' => {
1228             name => 'Microsoft.Windows.Common-Controls',
1229             type => 'win32',
1230             version => '6.0.0.0',
1231             publicKeyToken => '6595b64144ccf1df',
1232             language => '*',
1233             processorArchitecture => '*',
1234             },
1235             };
1236             return $dependencytemplates;
1237             }
1238              
1239             sub get_default_manifest { Win32::Exe::Manifest::Parser->get_default_manifest(); }
1240              
1241             sub get_default_schema { Win32::Exe::Manifest::Parser->get_default_schema(); }
1242              
1243              
1244             1;
1245              
1246             __END__