@@ -19,6 +19,7 @@ namespace HtmlPdf;
1919
2020using System ;
2121using System . Collections . Generic ;
22+ using System . IO ;
2223
2324[ Cmdlet ( VerbsData . Out , "Pdf" , ConfirmImpact = ConfirmImpact . Low , RemotingCapability = RemotingCapability . None , SupportsShouldProcess = true ) ]
2425public sealed class OutPdfCommand : PSCmdlet
@@ -54,6 +55,14 @@ public string? Title
5455 get ; set ;
5556 }
5657
58+ [ Parameter ( Position = 2 , ValueFromPipelineByPropertyName = true ) ]
59+ [ PSDefaultValue ( Value = PageSize . A4 ) ]
60+ public PageSize Size { get ; set ; } = PageSize . A4 ;
61+
62+ [ Parameter ( Position = 3 , ValueFromPipelineByPropertyName = true ) ]
63+ [ PSDefaultValue ( Value = PageOrientation . Portrait ) ]
64+ public PageOrientation Orientation { get ; set ; } = PageOrientation . Portrait ;
65+
5766 protected override void ProcessRecord ( )
5867 {
5968 if ( this . Page is { Length : > 0 } )
@@ -64,16 +73,149 @@ protected override void ProcessRecord()
6473
6574 protected override void EndProcessing ( )
6675 {
67- if ( _pages is { Count : > 0 } )
76+ if ( _pages is not { Count : > 0 } )
77+ {
78+ ErrorRecord erNone = new (
79+ new FileNotFoundException ( "Pages were not found." ) ,
80+ "NoPagesFound" ,
81+ ErrorCategory . ObjectNotFound ,
82+ targetObject : null ) ;
83+
84+ this . ThrowTerminatingError ( erNone ) ;
85+ return ;
86+ }
87+
88+ string outputPath ;
89+ try
90+ {
91+ outputPath = ResolveOutputPath ( ) ;
92+ }
93+ catch ( ItemNotFoundException ex )
94+ {
95+ var er = new ErrorRecord (
96+ ex ,
97+ "UnresolvedPath" ,
98+ ErrorCategory . ObjectNotFound ,
99+ targetObject : IsLiteralPath ? LiteralPath : Path ) ;
100+ this . ThrowTerminatingError ( er ) ;
101+ return ;
102+ }
103+ catch ( Exception ex ) when ( ex is ArgumentException or PSArgumentException or DirectoryNotFoundException )
104+ {
105+ var er = new ErrorRecord (
106+ ex ,
107+ "UnresolvedPath" ,
108+ ErrorCategory . InvalidArgument ,
109+ targetObject : IsLiteralPath ? LiteralPath : Path ) ;
110+ this . ThrowTerminatingError ( er ) ;
111+ return ;
112+ }
113+
114+ bool dryRun = ! this . ShouldProcess ( outputPath , "Create" ) ;
115+
116+ var notFoundPages = new List < string > ( capacity : _pages . Count ) ;
117+ var resolvedPages = new List < string > ( capacity : _pages . Count ) ;
118+
119+ foreach ( var page in _pages )
120+ {
121+ try
122+ {
123+ foreach ( var resolvedPage in this . SessionState . Path . GetResolvedPSPathFromPSPath ( page ) )
124+ {
125+ var rp = GetFilePathOfExistingFile ( this , resolvedPage . ProviderPath ) ;
126+ if ( rp is not null )
127+ {
128+ resolvedPages . Add ( rp ) ;
129+ }
130+ else
131+ {
132+ notFoundPages . Add ( page ) ;
133+ }
134+ }
135+ }
136+ catch ( ItemNotFoundException )
137+ {
138+ notFoundPages . Add ( page ) ;
139+ }
140+ }
141+
142+ if ( notFoundPages . Count > 0 )
143+ {
144+ ErrorRecord er = new (
145+ new FileNotFoundException ( "Pages were not found." ) ,
146+ "NoPagesFound" ,
147+ ErrorCategory . ObjectNotFound ,
148+ targetObject : notFoundPages ) ;
149+
150+ this . ThrowTerminatingError ( er ) ;
151+ }
152+
153+ string ? html = HtmlToPdfConversion . ConvertToPdf ( resolvedPages , this . Title ! , outputPath , this . Size , this . Orientation , dryRun ) ;
154+ this . WriteVerbose ( html ) ;
155+ }
156+
157+ private bool IsLiteralPath => string . Equals ( this . ParameterSetName , "LiteralPath" , StringComparison . Ordinal ) ;
158+
159+ private string ResolveOutputPath ( )
160+ {
161+ string ? raw = IsLiteralPath ? this . LiteralPath : this . Path ;
162+ if ( string . IsNullOrWhiteSpace ( raw ) )
68163 {
69- string path = string . Equals ( this . ParameterSetName , "Path" , StringComparison . Ordinal )
70- ? this . SessionState . Path . GetUnresolvedProviderPathFromPSPath ( this . Path ! )
71- : System . IO . Path . GetFullPath ( this . LiteralPath ! ) ;
164+ throw new PSArgumentNullException ( IsLiteralPath ? nameof ( this . LiteralPath ) : nameof ( this . Path ) ) ;
165+ }
166+
167+ // If the (non-literal) user input contains wildcards, resolve existing items
168+ if ( ! IsLiteralPath && WildcardPattern . ContainsWildcardCharacters ( raw ) )
169+ {
170+ var matches = this . GetResolvedProviderPathFromPSPath ( raw , out _ ) ;
171+ if ( matches . Count == 0 )
172+ {
173+ throw new ItemNotFoundException ( $ "Path '{ raw } ' could not be resolved.") ;
174+ }
175+
176+ if ( matches . Count > 1 )
177+ {
178+ throw new PSArgumentException ( $ "Path '{ raw } ' resolved to multiple locations. Use -LiteralPath to specify an exact output file.") ;
179+ }
180+
181+ // If the resolved path is a directory, you might choose to derive a filename; enforce it's not a container
182+ var singlePath = matches [ 0 ] ;
183+ if ( Directory . Exists ( singlePath ) )
184+ {
185+ throw new PSArgumentException ( $ "Path '{ raw } ' resolves to a directory. Specify a file name.") ;
186+ }
187+
188+ return singlePath ;
189+ }
72190
73- bool dryRun = ! this . ShouldProcess ( "Pdf" , "Create" ) ;
191+ // Treat as a (possibly new) file path. This yields the full provider path even if the file does not yet exist
192+ var full = this . SessionState . Path . GetUnresolvedProviderPathFromPSPath ( raw ) ;
74193
75- string ? html = HtmlToPdfConversion . ConvertToPdf ( _pages , this . Title ! , path , dryRun ) ;
76- this . WriteVerbose ( html ) ;
194+ // Validate directory existence (optional but safer)
195+ var dir = System . IO . Path . GetDirectoryName ( full ) ;
196+ if ( ! string . IsNullOrEmpty ( dir ) && ! Directory . Exists ( dir ) )
197+ {
198+ throw new DirectoryNotFoundException ( $ "Directory '{ dir } ' does not exist.") ;
77199 }
200+
201+ return full ;
202+ }
203+
204+ private static string ? GetFilePathOfExistingFile ( PSCmdlet cmdlet , string path )
205+ {
206+ var resolvedProviderPath = cmdlet . SessionState . Path . GetUnresolvedProviderPathFromPSPath ( path ) ;
207+ return File . Exists ( resolvedProviderPath ) ? resolvedProviderPath : null ;
78208 }
79209}
210+
211+ // See CSS @page size keyword values:
212+ // https://developer.mozilla.org/en-US/docs/Web/CSS/@page/size
213+ public enum PageSize
214+ {
215+ A5 , A4 , A3 , B5 , B4 , JISB5 , JISB4 , Letter , Legal , Ledger ,
216+ }
217+
218+ public enum PageOrientation
219+ {
220+ Portrait , Landscape ,
221+ }
0 commit comments