1+ using  Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ; 
2+ using  System ; 
3+ using  System . Collections ; 
4+ using  System . Collections . Generic ; 
5+ using  System . IO ; 
6+ using  System . Linq ; 
7+ using  System . Management . Automation ; 
8+ using  System . Management . Automation . Language ; 
9+ using  System . Reflection ; 
10+ 
11+ namespace  Microsoft . Windows . PowerShell . ScriptAnalyzer . Commands 
12+ { 
13+     /// <summary> 
14+     /// Creates a new PSScriptAnalyzer settings file in the specified directory 
15+     /// optionally based on a preset, a blank template, or all rules with default arguments. 
16+     /// </summary> 
17+     [ Cmdlet ( VerbsCommon . New ,  "ScriptAnalyzerSettingsFile" ,  SupportsShouldProcess  =  true ) ] 
18+     [ OutputType ( typeof ( string ) ) ] 
19+     public  sealed  class  NewScriptAnalyzerSettingsFileCommand  :  PSCmdlet ,  IOutputWriter 
20+     { 
21+         private  const  string  BaseOption_All  =  "All" ; 
22+         private  const  string  BaseOption_Blank  =  "Blank" ; 
23+ 
24+         /// <summary> 
25+         /// Target directory (or file path) where the settings file will be created. Defaults to 
26+         /// current location. 
27+         /// </summary> 
28+         [ Parameter ( Position  =  0 ) ] 
29+         [ ValidateNotNullOrEmpty ] 
30+         public  string  Path  {  get ;  set ;  } 
31+ 
32+         /// <summary> 
33+         /// Settings file format/extension (e.g. json, psd1). Defaults to first supported format. 
34+         /// </summary> 
35+         [ Parameter ] 
36+         [ ArgumentCompleter ( typeof ( FileFormatCompleter ) ) ] 
37+         [ ValidateNotNullOrEmpty ] 
38+         public  string  FileFormat  {  get ;  set ;  } 
39+ 
40+         /// <summary> 
41+         /// Base content: 'Blank', 'All', or a preset name returned by Get-SettingPresets. 
42+         /// 'Blank' -> minimal empty settings. 
43+         /// 'All'   -> include all rules and their configurable arguments with current defaults. 
44+         /// preset  -> copy preset contents. 
45+         /// </summary> 
46+         [ Parameter ] 
47+         [ ArgumentCompleter ( typeof ( SettingsBaseCompleter ) ) ] 
48+         [ ValidateNotNullOrEmpty ] 
49+         public  string  Base  {  get ;  set ;  }  =  BaseOption_Blank ; 
50+ 
51+         /// <summary> 
52+         /// Overwrite existing file if present. 
53+         /// </summary> 
54+         [ Parameter ] 
55+         public  SwitchParameter  Force  {  get ;  set ;  } 
56+ 
57+         protected  override  void  BeginProcessing ( ) 
58+         { 
59+             Helper . Instance  =  new  Helper ( SessionState . InvokeCommand ) ; 
60+             Helper . Instance . Initialize ( ) ; 
61+ 
62+             string [ ]  rulePaths  =  Helper . ProcessCustomRulePaths ( null ,  SessionState ,  false ) ; 
63+             ScriptAnalyzer . Instance . Initialize ( this ,  rulePaths ,  null ,  null ,  null ,  null  ==  rulePaths ) ; 
64+         } 
65+ 
66+         protected  override  void  ProcessRecord ( ) 
67+         { 
68+             // Default Path 
69+             if  ( string . IsNullOrWhiteSpace ( Path ) ) 
70+             { 
71+                 Path  =  SessionState . Path . CurrentFileSystemLocation . ProviderPath ; 
72+             } 
73+ 
74+             // If user passed an existing file path, switch to its directory. 
75+             if  ( File . Exists ( Path ) ) 
76+             { 
77+                 Path  =  System . IO . Path . GetDirectoryName ( Path ) ; 
78+             } 
79+ 
80+             // Require the directory to already exist (do not create it). 
81+             if  ( ! Directory . Exists ( Path ) ) 
82+             { 
83+                 ThrowTerminatingError ( new  ErrorRecord ( 
84+                     new  DirectoryNotFoundException ( $ "Directory '{ Path } ' does not exist.") , 
85+                     "DIRECTORY_NOT_FOUND" , 
86+                     ErrorCategory . ObjectNotFound , 
87+                     Path ) ) ; 
88+                 return ; 
89+             } 
90+ 
91+             // Ensure FileSystem provider for target Path. 
92+             ProviderInfo  providerInfo ; 
93+             try 
94+             { 
95+                 SessionState . Path . GetResolvedProviderPathFromPSPath ( Path ,  out  providerInfo ) ; 
96+             } 
97+             catch  ( Exception  ex ) 
98+             { 
99+                 ThrowTerminatingError ( new  ErrorRecord ( 
100+                     new  InvalidOperationException ( $ "Cannot resolve path '{ Path } ': { ex . Message } ",  ex ) , 
101+                     "PATH_RESOLVE_FAILED" , 
102+                     ErrorCategory . InvalidArgument , 
103+                     Path ) ) ; 
104+                 return ; 
105+             } 
106+ 
107+             if  ( ! string . Equals ( providerInfo . Name ,  "FileSystem" ,  StringComparison . OrdinalIgnoreCase ) ) 
108+             { 
109+                 ThrowTerminatingError ( new  ErrorRecord ( 
110+                     new  InvalidOperationException ( "Target path must be in the FileSystem provider." ) , 
111+                     "INVALID_PROVIDER" , 
112+                     ErrorCategory . InvalidArgument , 
113+                     Path ) ) ; 
114+             } 
115+ 
116+             // Default format to first supported. 
117+             if  ( string . IsNullOrWhiteSpace ( FileFormat ) ) 
118+             { 
119+                 FileFormat  =  Settings . GetSettingsFormats ( ) . First ( ) ; 
120+             } 
121+ 
122+             // Validate requested format. 
123+             if  ( ! Settings . GetSettingsFormats ( ) . Any ( f =>  string . Equals ( f ,  FileFormat ,  StringComparison . OrdinalIgnoreCase ) ) ) 
124+             { 
125+                 ThrowTerminatingError ( new  ErrorRecord ( 
126+                     new  ArgumentException ( $ "Unsupported settings format '{ FileFormat } '.") , 
127+                     "UNSUPPORTED_FORMAT" , 
128+                     ErrorCategory . InvalidArgument , 
129+                     FileFormat ) ) ; 
130+             } 
131+ 
132+             var  targetFile  =  System . IO . Path . Combine ( Path ,  $ "{ Settings . DefaultSettingsFileName } .{ FileFormat } ") ; 
133+ 
134+             if  ( File . Exists ( targetFile )  &&  ! Force ) 
135+             { 
136+                 WriteWarning ( $ "Settings file already exists: { targetFile } . Use -Force to overwrite.") ; 
137+                 return ; 
138+             } 
139+ 
140+             SettingsData  data ; 
141+             try 
142+             { 
143+                 data  =  BuildSettingsData ( ) ; 
144+             } 
145+             catch  ( Exception  ex ) 
146+             { 
147+                 ThrowTerminatingError ( new  ErrorRecord ( 
148+                     ex , 
149+                     "BUILD_SETTINGS_FAILED" , 
150+                     ErrorCategory . InvalidData , 
151+                     Base ) ) ; 
152+                 return ; 
153+             } 
154+ 
155+             string  content ; 
156+             try 
157+             { 
158+                 content  =  Settings . Serialize ( data ,  FileFormat ) ; 
159+             } 
160+             catch  ( Exception  ex ) 
161+             { 
162+                 ThrowTerminatingError ( new  ErrorRecord ( 
163+                     ex , 
164+                     "SERIALIZE_FAILED" , 
165+                     ErrorCategory . InvalidData , 
166+                     FileFormat ) ) ; 
167+                 return ; 
168+             } 
169+ 
170+             if  ( ShouldProcess ( targetFile ,  "Create settings file" ) ) 
171+             { 
172+                 try 
173+                 { 
174+                     File . WriteAllText ( targetFile ,  content ) ; 
175+                     WriteVerbose ( $ "Created settings file: { targetFile } ") ; 
176+                 } 
177+                 catch  ( Exception  ex ) 
178+                 { 
179+                     ThrowTerminatingError ( new  ErrorRecord ( 
180+                         ex , 
181+                         "CREATE_FILE_FAILED" , 
182+                         ErrorCategory . InvalidData , 
183+                         targetFile ) ) ; 
184+                     return ; 
185+                 } 
186+                 WriteObject ( targetFile ) ; 
187+             } 
188+         } 
189+ 
190+         private  SettingsData  BuildSettingsData ( ) 
191+         { 
192+             if  ( string . Equals ( Base ,  BaseOption_Blank ,  StringComparison . OrdinalIgnoreCase ) ) 
193+             { 
194+                 return  new  SettingsData ( ) ;  // empty snapshot 
195+             } 
196+ 
197+             if  ( string . Equals ( Base ,  BaseOption_All ,  StringComparison . OrdinalIgnoreCase ) ) 
198+             { 
199+                 return  BuildAllSettingsData ( ) ; 
200+             } 
201+ 
202+             // Preset 
203+             var  presetPath  =  Settings . TryResolvePreset ( Base ) ; 
204+             if  ( presetPath  ==  null ) 
205+             { 
206+                 throw  new  FileNotFoundException ( $ "Preset '{ Base } ' not found.") ; 
207+             } 
208+             return  Settings . Create ( presetPath ) ; 
209+         } 
210+ 
211+         private  SettingsData  BuildAllSettingsData ( ) 
212+         { 
213+             var  ruleNames  =  new  List < string > ( ) ; 
214+             var  ruleArgs  =  new  Dictionary < string ,  Dictionary < string ,  object > > ( StringComparer . OrdinalIgnoreCase ) ; 
215+ 
216+             var  modNames  =  ScriptAnalyzer . Instance . GetValidModulePaths ( ) ; 
217+             var  rules  =  ScriptAnalyzer . Instance . GetRule ( modNames ,  null )  ??  Enumerable . Empty < IRule > ( ) ; 
218+ 
219+             foreach  ( var  rule  in  rules ) 
220+             { 
221+                 var  name  =  rule . GetName ( ) ; 
222+                 ruleNames . Add ( name ) ; 
223+ 
224+                 if  ( rule  is  ConfigurableRule  configurable ) 
225+                 { 
226+                     var  props  =  rule . GetType ( ) . GetProperties ( BindingFlags . Instance  |  BindingFlags . Public ) ; 
227+                     var  argDict  =  new  Dictionary < string ,  object > ( StringComparer . OrdinalIgnoreCase ) ; 
228+                     foreach  ( var  p  in  props ) 
229+                     { 
230+                         if  ( p . GetCustomAttribute < ConfigurableRulePropertyAttribute > ( inherit :  true )  ==  null ) 
231+                         { 
232+                             continue ; 
233+                         } 
234+                         argDict [ p . Name ]  =  p . GetValue ( rule ) ; 
235+                     } 
236+                     if  ( argDict . Count  >  0 ) 
237+                     { 
238+                         ruleArgs [ name ]  =  argDict ; 
239+                     } 
240+                 } 
241+             } 
242+ 
243+             return  new  SettingsData 
244+             { 
245+                 IncludeRules  =  ruleNames , 
246+                 RuleArguments  =  ruleArgs , 
247+             } ; 
248+         } 
249+ 
250+         #region Completers
251+ 
252+         private  sealed  class  FileFormatCompleter  :  IArgumentCompleter 
253+         { 
254+             public  IEnumerable < CompletionResult >  CompleteArgument ( string  commandName , 
255+                 string  parameterName ,  string  wordToComplete ,  CommandAst  commandAst , 
256+                 IDictionary  fakeBoundParameters ) 
257+             { 
258+                 foreach  ( var  fmt  in  Settings . GetSettingsFormats ( ) ) 
259+                 { 
260+                     if  ( fmt . StartsWith ( wordToComplete  ??  string . Empty ,  StringComparison . OrdinalIgnoreCase ) ) 
261+                     { 
262+                         yield  return  new  CompletionResult ( fmt ,  fmt ,  CompletionResultType . ParameterValue ,  $ "Settings format '{ fmt } '") ; 
263+                     } 
264+                 } 
265+             } 
266+         } 
267+ 
268+         private  sealed  class  SettingsBaseCompleter  :  IArgumentCompleter 
269+         { 
270+             public  IEnumerable < CompletionResult >  CompleteArgument ( string  commandName , 
271+                 string  parameterName ,  string  wordToComplete ,  CommandAst  commandAst , 
272+                 IDictionary  fakeBoundParameters ) 
273+             { 
274+                 var  bases  =  new  List < string >  {  BaseOption_Blank ,  BaseOption_All  } ; 
275+                 bases . AddRange ( Settings . GetSettingPresets ( ) ) ; 
276+ 
277+                 foreach  ( var  b  in  bases ) 
278+                 { 
279+                     if  ( b . StartsWith ( wordToComplete  ??  string . Empty ,  StringComparison . OrdinalIgnoreCase ) ) 
280+                     { 
281+                         yield  return  new  CompletionResult ( b ,  b ,  CompletionResultType . ParameterValue ,  $ "Base template '{ b } '") ; 
282+                     } 
283+                 } 
284+             } 
285+         } 
286+ 
287+         #endregion
288+     } 
289+ } 
0 commit comments