1+ using namespace System.Management.Automation.Language
2+ <#
3+ . SYNOPSIS
4+ all keyword
5+ . DESCRIPTION
6+ The all keyword is a powerful way to accomplish several useful scenarios with a very natural syntax.
7+
8+ `all` can get all of a set of things that match a criteria and run one or more post-conditions.
9+ . EXAMPLE
10+ & {
11+ $glitters = @{glitters=$true}
12+ all that glitters
13+ }.Transpile()
14+ . EXAMPLE
15+ function mallard([switch]$Quack) { $Quack }
16+ Get-Command mallard | Get-Member | Select-Object -ExpandProperty TypeName -Unique
17+ . {all functions that quack are ducks}.Transpile()
18+ Get-Command mallard | Get-Member | Select-Object -ExpandProperty TypeName -Unique
19+ . EXAMPLE
20+
21+ . {
22+ $numbers = 1..100
23+ $null = all $numbers where { ($_ % 2) -eq 1 } are odd
24+ $null = all $numbers where { ($_ % 2) -eq 0 } are even
25+ }.Transpile()
26+
27+ @(
28+ . { all even $numbers }.Transpile()
29+ ).Length
30+
31+ @(
32+ . { all odd $numbers }.Transpile()
33+ ).Length
34+
35+
36+ #>
37+ [ValidateScript ({
38+ $validateVar = $_
39+ if ($validateVar -is [CommandAst ]) {
40+ $cmdAst = $validateVar
41+ if ($cmdAst.CommandElements [0 ].Value -eq ' all' ) {
42+ return $true
43+ }
44+ }
45+ return $false
46+ })]
47+ param (
48+ # If set, include all functions in the input.
49+ [Alias (' Function' )]
50+ [switch ]
51+ $Functions ,
52+
53+ # If set, include all commands in the input.
54+ [Alias (' Command' )]
55+ [switch ]
56+ $Commands ,
57+
58+ # If set, include all cmdlets in the input
59+ [Alias (' Cmdlet' )]
60+ [switch ]
61+ $Cmdlets ,
62+
63+ # If set, include all aliases in the input
64+ [Alias (' Alias' )]
65+ [switch ]
66+ $Aliases ,
67+
68+ # If set, include all applications in the input
69+ [Alias (' Application' )]
70+ [switch ]
71+ $Applications ,
72+
73+ # If set, include all variables in the inputObject.
74+ [Parameter ()]
75+ [Alias (' Variable' )]
76+ [switch ]
77+ $Variables ,
78+
79+ # If set, will include all of the variables, aliases, functions, and scripts in the current directory.
80+ [Parameter ()]
81+ [Alias (' Thing' )]
82+ [switch ]
83+ $Things ,
84+
85+ # The input to be searched.
86+ [Parameter (ValueFromPipelineByPropertyName , Position = 0 )]
87+ [Alias (' In' , ' Of' , ' The' , ' Object' )]
88+ $InputObject ,
89+
90+ # An optional condition
91+ [Parameter (ValueFromPipelineByPropertyName , Position = 1 )]
92+ [Alias (' That' , ' Condition' )]
93+ $Where ,
94+
95+ # The action that will be run
96+ [Parameter (ValueFromPipelineByPropertyName , Position = 2 )]
97+ [Alias (' Is' , ' Are' , ' Foreach' , ' Can' , ' Could' , ' Should' )]
98+ $For ,
99+
100+ # The Command AST
101+ [Parameter (Mandatory , ParameterSetName = ' CommandAST' , ValueFromPipeline )]
102+ [CommandAst ]
103+ $CommandAst
104+ )
105+
106+ process {
107+
108+ # Gather some information about our calling context
109+ $myParams = [Ordered ]@ {} + $PSBoundParameters
110+ # and attempt to parse it as a sentance (only allowing it to match this particular command)
111+ $mySentence = $commandAst.AsSentence ($MyInvocation.MyCommand )
112+ $myCmd = $MyInvocation.MyCommand
113+ $myCmdName = $myCmd.Name
114+
115+ # Determine how many times we've been recursively called, so we can disambiguate variables later.
116+ $callstack = Get-PSCallStack
117+ $callCount = @ ($callstack |
118+ Where-Object { $_.InvocationInfo.MyCommand.Name -eq $myCmdName }).count - 1
119+
120+ # Walk thru all mapped parameters in the sentence
121+ foreach ($paramName in $mySentence.Parameters.Keys ) {
122+ if (-not $myParams [$paramName ]) { # If the parameter was not directly supplied
123+ $myParams [$paramName ] = $mySentence.Parameters [$paramName ] # grab it from the sentence.
124+ foreach ($myParam in $myCmd.Parameters.Values ) {
125+ if ($myParam.Aliases -contains $paramName ) { # set any variables that share the name of an alias
126+ $ExecutionContext.SessionState.PSVariable.Set ($myParam.Name , $mySentence.Parameters [$paramName ])
127+ }
128+ }
129+ # and set this variable for this value.
130+ $ExecutionContext.SessionState.PSVariable.Set ($paramName , $mySentence.Parameters [$paramName ])
131+ }
132+ }
133+
134+ # Now all of the remaining code in this transpiler should act as if we called it from the command line.
135+
136+ # Nowe we need to set up the input set
137+ $inputSet = @ (
138+ $commandTypes = [Management.Automation.CommandTypes ]0
139+ foreach ($myParam in $myCmd.Parameters.Values ) {
140+ if ($myParam.ParameterType -eq [switch ] -and
141+ $ExecutionContext.SessionState.PSVariable.Get ($myParam.Name ).Value) {
142+ if ($myParam.Name -replace ' e?s$' -as [Management.Automation.CommandTypes ]) {
143+ $commandTypes = $commandTypes -bor [Management.Automation.CommandTypes ]($myParam.Name -replace ' e?s$' )
144+ }
145+ elseif ($myParam.Name -eq ' Things' ) {
146+ $commandTypes = $commandTypes -bor [Management.Automation.CommandTypes ]' Alias,Function,Filter,Cmdlet'
147+ }
148+ elseif ($myParam.Name -eq ' Scripts' ) {
149+ $commandTypes = $commandTypes -bor [Management.Automation.CommandTypes ]' ExternalScript'
150+ }
151+ }
152+ }
153+
154+ if ($commandTypes ) {
155+ [ScriptBlock ]::create(" `$ executionContext.SessionState.InvokeCommand.GetCommands('*','$commandTypes ',`$ true)" )
156+ }
157+ if ($variables -or $Things ) {
158+ {Get-ChildItem - Path variable:}
159+ }
160+ if ($InputObject ) {
161+ if ($InputObject -is [Ast ]) {
162+ if ($InputObject -is [ScriptBlockExpressionAst ]) {
163+ $InputObject.ConvertFromAST ()
164+ } else {
165+ $InputObject.Extent.ToString ()
166+ }
167+ } else {
168+ $InputObject
169+ }
170+ }
171+ )
172+
173+ # If the sentence had unbound arguments
174+ if ($mySentence.Arguments ) {
175+ if (-not $inputSet ) { # and we had not yet set input
176+ $inputSet =
177+ foreach ($sentanceArg in $mySentence.Arguments ) {
178+ # then anything that is not a [string] or [ScriptBlock] will become input
179+ if ($sentanceArg -isnot [string ] -and $sentanceArg -isnot [ScriptBlock ]) {
180+ $sentanceArg
181+ } else {
182+ # and [strings]s and [ScriptBlock]s will become -Where parameters.
183+ if (-not $Where ) {
184+ $Where = $sentanceArg
185+ }
186+ else {
187+ $where = @ ($Where ) + $sentanceArg
188+ }
189+ }
190+ }
191+ }
192+ }
193+
194+
195+ # If we still don't have an inputset, default it to 'things'
196+ if (-not $InputSet ) {
197+ $InputSet =
198+ if ($mySentence.Arguments ) {
199+ $mySentence.Arguments
200+ } else {
201+ {$ExecutionContext.SessionState.InvokeCommand.GetCommands (' *' , ' Alias,Function,Filter,Cmdlet' , $true )},
202+ {Get-ChildItem - Path variable:}
203+ }
204+ }
205+
206+ # Note: there's still a lot of room for this syntax to grow and become even more natural.
207+
208+ # But with most of our arguments in hand, now we're ready to create the script
209+
210+ # region Generate Script
211+ $generatedScript = @ (
212+
213+ # Create an input collection with all of our input
214+ '
215+ # Collect all items into an input collection
216+ $inputCollection =' + $ (
217+ @ (foreach ($setOfInput in $inputSet ) {
218+ if ($setOfInput -is [ScriptBlock ]) {
219+ ' $(' + [Environment ]::NewLine +
220+ $setOfInput + [Environment ]::NewLine + ' )'
221+ } else {
222+ " `$ ($setOfInput )"
223+ }
224+ }) -join (' ,' + [Environment ]::NewLine + ' ' )
225+ )
226+
227+ "
228+ # 'unroll' the collection by iterating over it once.
229+ `$ filteredCollection = `$ inputCollection =
230+ @(foreach (`$ in in `$ inputCollection) { `$ in })
231+ "
232+
233+ if ($Where ) {
234+ @ (
235+ # If -Where was provided, filter the input
236+
237+ "
238+ # Since filtering conditions have been passed, we must filter item-by-item
239+ `$ filteredCollection = foreach (`$ item in `$ inputCollection) {
240+ # we set `$ this, `$ psItem, and `$ _ for ease-of-use.
241+ `$ this = `$ _ = `$ psItem = `$ item
242+ "
243+ foreach ($wh in $where ) {
244+ if ($wh -is [ScriptBlockExpressionAst ]) {
245+ $wh = $wh.ConvertFromAST ()
246+ }
247+ if ($wh -is [ScriptBlock ] -or $wh -is [Ast ]) {
248+ " if (-not `$ ($ ( $wh.Transpile ())
249+ )) { continue } "
250+ }
251+ elseif ($wh -is [string ]) {
252+ $safeStr = $ ($wh -replace " '" , " ''" )
253+ " if (-not ( # Unless it
254+ (`$ null -ne `$ item.'$safeStr ') -or # has a '$safeStr ' property
255+ (`$ null -ne `$ item.value.'$safeStr ') -or # or it's value has the property '$safeStr '
256+ (`$ null -ne `$ item.Parameters.'$safeStr ') -or # or it's parameters have the property '$safeStr '
257+ (`$ item.pstypenames -contains '$safeStr ') # or it's typenames have the property '$safeStr '
258+ )) {
259+ continue # keep moving
260+ }"
261+ }
262+ }
263+ "
264+ `$ item
265+ }"
266+ )
267+ }
268+
269+
270+ if ($For ) {
271+ # If -For was
272+ "
273+ # Walk over each item in the filtered collection
274+ foreach (`$ item in `$ filteredCollection) {
275+ # we set `$ this, `$ psItem, and `$ _ for ease-of-use.
276+ `$ this = `$ _ = `$ psItem = `$ item
277+ "
278+ foreach ($fo in $for ) {
279+ if ($fo -is [ScriptBlockExpressionAst ]) {
280+ $fo = $fo.ConvertFromAST ()
281+ }
282+
283+ if ($fo -is [ScriptBlock ] -or $fo -is [Ast ]) {
284+ $fo.Transpile ()
285+ }
286+
287+ if ($fo -is [string ]) {
288+ $safeStr = $fo -replace " '" , " ''"
289+ "
290+ if (`$ item.value -and `$ item.value.pstypenames.insert) {
291+ if (`$ item.value.pstypenames -notcontains '$safeStr ') {
292+ `$ item.value.pstypenames.insert(0, '$safeStr ')
293+ }
294+ }
295+ elseif (`$ item.pstypenames.insert -and `$ item.pstypenames -notcontains '$safeStr ') {
296+ `$ item.pstypenames.insert(0, '$safeStr ')
297+ }
298+ "
299+ }
300+
301+ }
302+
303+ "
304+ `$ item
305+ }
306+ "
307+ } else {
308+ " `$ filteredCollection"
309+ }
310+ )
311+
312+ # endregion Generate Script
313+
314+ # If the command was assigned or piped from, wrap the script in a subexpression
315+ if ($CommandAst.IsAssigned -or $CommandAst.PipelinePosition -lt $CommandAst.PipelineLength ) {
316+ $generatedScript = " `$ ($ ( $generatedScript -join [Environment ]::NewLine) )"
317+ }
318+ # If the command was piped to, wrap the script in a command expression.
319+ if ($CommandAst.IsPiped ) {
320+ $generatedScript = " & { process {
321+ $generatedScript
322+ } }"
323+ }
324+
325+ # Generate the scriptblock
326+ $generatedScript = [ScriptBlock ]::create(
327+ $generatedScript -join [Environment ]::NewLine
328+ )
329+
330+ if (-not $generatedScript ) { return }
331+
332+ # Rename the variables in the generated script, using our callstack count.
333+ .> RenameVariable - ScriptBlock $generatedScript - VariableRename @ {
334+ ' item' = " $ ( ' _' * $callcount ) item"
335+ " filteredCollection" = " $ ( ' _' * $callcount ) filteredCollection"
336+ " inputCollection" = " $ ( ' _' * $callcount ) inputCollection"
337+ }
338+ }
0 commit comments