about_Simplified_Syntax

TOPIC  
    about_Simplified_Syntax  

SHORT DESCRIPTION  
    Describes easier, more natural-language ways of scripting filters for  
    collections of objects.  

LONG DESCRIPTION  
    Simplified syntax, introduced in Windows PowerShell 3.0, lets you build some  
    filter commands without using script blocks. The simplified syntax more  
    closely resembles natural language, and is primarily useful with collections  
    of objects that are piped into Where-Object and ForEach-Object commands, and  
    with the Where and ForEach language keywords.  

    You can use a method on the members of a collection (most commonly, an array)  
    without either containing it in a script block, or adding the automatic  
    variable "$_."  

  Syntax          
      Compare the following two examples. The first shows the proper   
      construction of a filtering command in Windows PowerShell 1.0 and 2.0.  

          $<collection> | Where-Object  
foreach ($<item> in $<collection>){<statement list>}  

  Simplified syntax  
      Starting in Windows PowerShell 3.0, syntax with language keywords such  
      as Where and ForEach was simplified. Comparison operators that work on  
      the members of a collection are treated as parameters. You can use a method  
      on the members of a collection without containing it in a script block  
      or adding the automatic variable "$_.". Consider the following two examples:  

          dir cert:\ -Recurse | foreach GetKeyAlgorithm  
          dir cert:\ -Recurse | foreach {$_.GetKeyAlgorithm()}  

      Although both commands work, the first returns results without using  
      a script block or the $_. automatic variable. The method GetKeyAlgorithm  
      is treated as a parameter of ForEach. The first command returns the same results,  
      but without errors, because the simplified syntax does not attempt to return  
      results for items for which the specified argument did not apply.  

      In this example, the Get-Process property Description is passed as a parameter  
      argument of the ForEach statement. The results are the descriptions of active  
      processes.  

          Get-Process | ForEach Description  

  The Foreach statement outside a command pipeline  
      The part of the Foreach statement enclosed in parenthesis represents a  
      variable and a collection to iterate. Windows PowerShell creates the   
      variable ($<item>) automatically when the Foreach loop runs. Prior to   
      each iteration through the loop, the variable is set to a value in the   
      collection. The block following a Foreach statement {<statement list>}  
      contains a set of commands to execute against each item in a collection.  

  Examples  
      For example, the Foreach loop in the following example displays the   
      values in the $letterArray array.  

          $letterArray = "a","b","c","d"  
          foreach ($letter in $letterArray)  
          {  
              Write-Host $letter  
          }  

      In this example, the $letterArray array is created and initialized with   
      the string values "a", "b", "c", and "d". The first time the Foreach   
      statement runs, it sets the $letter variable equal to the first item in   
      $letterArray ("a"). Then, it uses the Write-Host cmdlet to display the   
      letter a. The next time through the loop, $letter is set to "b", and so   
      on. After the Foreach loop displays the letter d, Windows PowerShell   
      exits the loop.  

      The entire Foreach statement must appear on a single line to run it as a  
      command at the Windows PowerShell command prompt. The entire Foreach   
      statement does not have to appear on a single line if you place the   
      command in a .ps1 script file instead.  

      Foreach statements can also be used together with cmdlets that   
      return a collection of items. In the following example, the Foreach   
      statement steps through the list of items that is returned by the   
      Get-ChildItem cmdlet.  

          foreach ($file in Get-ChildItem)  
          {  
              Write-Host $file  
          }  

      You can refine the example by using an If statement to limit the results   
      that are returned. In the following example, the Foreach statement   
      performs the same looping operation as the previous example, but it adds  
      an If statement to limit the results to files that are greater than 100   
      kilobytes (KB):  

          foreach ($file in Get-ChildItem)  
          {  
              if ($file.length -gt 100KB)   
              {  
                  Write-Host $file  
              }  
          }  

      In this example, the Foreach loop uses a property of the $file variable  
      to perform a comparison operation ($file.length -gt 100KB). The $file   
      variable contains all the properties in the object that is returned by   
      the Get-ChildItem cmdlet. Therefore, you can return more than just a   
      file name. In the next example, Windows PowerShell returns the length and  
      the last access time inside the statement list:  

          foreach ($file in Get-ChildItem)  
          {  
              if ($file.length -gt 100KB)   
              {  
                  Write-Host $file  
                  Write-Host $file.length  
                  Write-Host $file.lastaccesstime  
              }  
          }  

      In this example, you are not limited to running a single command in a   
      statement list.  

      You can also use a variable outside a Foreach loop and increment the   
      variable inside the loop. The following example counts files over 100 KB  
      in size:  

          $i = 0  
          foreach ($file in Get-ChildItem)  
          {  
              if ($file.length -gt 100KB)   
              {  
                  Write-Host $file "file size:" ($file.length /   
          1024).ToString("F0") KB  
                  $i = $i + 1  
              }  
          }  

          if ($i -ne 0)  
          {  
              Write-Host  
              Write-Host $i " file(s) over 100 KB in the current   
          directory."}  
          else   
          {  
              Write-Host "No files greater than 100 KB in the current   
          directory."  
          }  

      In the preceding example, the $i variable is set to 0 outside the loop,  
      and the variable is incremented inside the loop for each file that is  
      found that is larger than 100 KB. When the loop exits, an If statement  
      evaluates the value of $i to display a count of all the files over   
      100 KB. Or, it displays a message stating that no files over 100 KB were  
      found.  

      The previous example also demonstrates how to format the file length   
      results:  

          ($file.length / 1024).ToString("F0")  

      The value is divided by 1,024 to show the results in kilobytes rather   
      than bytes, and the resulting value is then formatted using the   
      fixed-point format specifier to remove any decimal values from the   
      result. The 0 makes the format specifier show no decimal places.  

  The Foreach Statement Inside a Command Pipeline  
      When Foreach appears in a command pipeline, Windows PowerShell uses the  
      foreach alias, which calls the ForEach-Object command. When you use   
      the foreach alias in a command pipeline, you do not include   
      the ($<item> in $<collection>) syntax as you do with the Foreach   
      statement. This is because the prior command in the pipeline provides   
      this information. The syntax of the foreach alias when used in a command  
      pipeline is as follows:  

          <command> | foreach {<command_block>}  

      For example, the Foreach loop in the following command displays processes  
      whose working set (memory usage) is greater than 20 megabytes (MB).   

      The Get-Process command gets all of the processes on the computer. The   
      Foreach alias performs the commands in the script block on each process  
      in sequence.   

      The IF statement selects processes with a working set (WS) greater than  
      20 megabytes. The Write-Host cmdlet writes the name of the process followed  
      by a colon. It divides the working set value, which is stored in bytes  
      by 1 megabyte to get the working set value in megabytes. Then it converts the  
      result from a double to a string. It displays the value as a fixed point  
      number with zero decimals (F0), a space separator (" "), and then "MB".  

          Write-Host "Processes with working sets greater than 20 MB."  
          Get-Process | foreach {   
             if ($_.WS -gt 20MB)  
             { Write-Host $_.name ": " ($_.WS/1MB).ToString("F0") MB -Separator ""}  
          }  

      The foreach alias also supports beginning command blocks, middle command  
      blocks, and end command blocks. The beginning and end command blocks run  
      once, and the middle command block runs every time the Foreach loop steps  
      through a collection or array.  

      The syntax of the foreach alias when used in a command pipeline with a   
      beginning, middle, and ending set of command blocks is as follows:  

          <command> | foreach {<beginning command_block>}{<middle   
          command_block>}{<ending command_block>}  

      The following example demonstrates the use of the beginning, middle, and  
      end command blocks.  

          Get-ChildItem | foreach {  
          $fileCount = $directoryCount = 0}{  
          if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}{  
          "$directoryCount directories and $fileCount files"}  

      The beginning block creates and initializes two variables to 0:  

          {$fileCount = $directoryCount = 0}  

      The middle block evaluates whether each item returned by Get-ChildItem  
      is a directory or a file:  

          {if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}  

      If the item that is returned is a directory, the $directoryCount   
      variable is incremented by 1. If the item is not a directory,   
      the $fileCount variable is incremented by 1. The ending block runs after  
      the middle block completes its looping operation and then returns the   
      results of the operation:  

          {"$directoryCount directories and $fileCount files"}  

      By using the beginning, middle, and ending command block structure and   
      the pipeline operator, you can rewrite the earlier example to find any   
      files that are greater than 100 KB, as follows:  

          Get-ChildItem | foreach{  
              $i = 0}{  
              if ($_.length -gt 100KB)  
              {  
                  Write-Host $_.name "file size:" ($_.length /   
          1024).ToString("F0") KB  
                  $i++  
              }  
              }{  
              if ($i -ne 0)  
              {  
                  Write-Host  
                  Write-Host "$i file(s) over 100 KB in the current   
          directory."  
              }  
              else   
              {  
              Write-Host "No files greater than 100 KB in the current   
          directory."}  
              }  

      The following example, a function which returns the functions that are  
      used in scripts and script modules, demonstrates how to use the MoveNext  
      method (which works similarly to "skip X" on a For loop) and the Current  
      property of the $foreach variable inside of a foreach script block, even  
      if there are unusually- or inconsistently-spaced function definitions  
      that span multiple lines to declare the function name. The example also works  
      if there are comments in the functions used in a script or script module.  

         function Get-FunctionPosition {  
             [CmdletBinding()]  
             [OutputType('FunctionPosition')]  
             param(  
                 [Parameter(Position=0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]  
                 [ValidateNotNullOrEmpty()]  
                 [Alias('PSPath')]  
                 [System.String[]]  
                 $Path  
             )  
             process {  
                 try {  
                     $filesToProcess = if ($_ -is [System.IO.FileSystemInfo]) {  
                         $_  
                     } else {  
                         Get-Item -Path $Path  
                     }  
                     foreach ($item in $filesToProcess) {  
                         if ($item.PSIsContainer -or $item.Extension -notin @('.ps1','.psm1')) {  
                             continue  
                         }  
                         $tokens = $errors = $null  
                         $ast = [System.Management.Automation.Language.Parser]::ParseFile($item.FullName,([REF]$tokens),([REF]$errors))  
                         if ($errors) {  
                             Write-Warning "File '$($item.FullName)' has $($errors.Count) parser errors."  
                         }  
                         :tokenLoop foreach ($token in $tokens) {  
                             if ($token.Kind -ne 'Function') {  
                                 continue  
                             }  
                             $position = $token.Extent.StartLineNumber  
                             do {  
                                 if (-not $foreach.MoveNext()) {  
                                     break tokenLoop  
                                 }  
                                 $token = $foreach.Current  
                             } until ($token.Kind -in @('Generic','Identifier'))  
                             $functionPosition = [pscustomobject]@{  
                                       Name = $token.Text  
                                 LineNumber = $position  
                                       Path = $item.FullName  
                             }  
                             Add-Member -InputObject $functionPosition -TypeName FunctionPosition -PassThru  
                         }  
                     }  
                 } catch {  
                     throw  
                 }  
             }  
         }  

SEE ALSO  
    about_Foreach  
    Where-Object  
    Foreach-Object