Home Cloud & Networking Windows Server Automation with PowerShell Cookbook - Fourth Edition

Windows Server Automation with PowerShell Cookbook - Fourth Edition

By Thomas Lee
books-svg-icon Book
eBook $75.99 $52.99
Print $93.99 $55.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $75.99 $52.99
Print $93.99 $55.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Introducing PowerShell 7
About this book
With a foreword from PowerShell creator Jeffrey Snover, this heavily updated edition is designed to help you learn how to use PowerShell 7.1 effectively and manage the core roles, features, and services of Windows Server in an enterprise setting. All scripts are compatible with both Window Server 2022 and 2019. This latest edition equips you with over 100 recipes you'll need in day-to-day work, covering a wide range of fundamental and more advanced use cases. We look at how to install and configure PowerShell 7.1, along with useful new features and optimizations, and how the PowerShell compatibility solution bridges the gap to older versions of PowerShell. Topics include using PowerShell to manage networking and DHCP in Windows Server, objects in Active Directory, Hyper-V, and Azure. Debugging is crucial, so the book shows you how to use some powerful tools to diagnose and resolve issues with Windows Server.
Publication date:
July 2021
Publisher
Packt
Pages
674
ISBN
9781800568457

 

Introducing PowerShell 7

This chapter covers the following recipes:

  • Exploring new operators
  • Exploring parallel processing with ForEach-Object
  • Exploring Improvements in Foreach and ForEach-Object
  • Improvements in Test-Connection
  • Using Select-String
  • Exploring the error view and Get-Error
  • Exploring experimental features
 

Introduction

In Chapter 1, Installing and Configuring PowerShell 7.1, you installed and configured PowerShell 7, along with VS Code and a new font. In this chapter, we look at PowerShell 7 and how it differs from Windows PowerShell. The recipes in this chapter illustrate some of the important new features that come with PowerShell 7.

Now that PowerShell is cross-platform, it has a new, expanded audience, one with a background in Linux shells such as Bash. With PowerShell 7, the PowerShell team added several new operators that improved parity with other shells and made life that little bit easier for IT pros.

With the move to open source, the PowerShell code was open to inspection by the community. Many talented developers were able to make improvements to performance and functionality. One example is how PowerShell performs iteration using ForEach and ForEach-Object. In Windows PowerShell, the ForEach syntax item and the Foreach-Object command allowed you to process collections of objects. With Windows PowerShell, each iteration through a collection was serial, which could result in long script runtimes. PowerShell 7 introduces an improvement in the ForEach-Object command that enables you to run iterations in parallel. This review has led to a reduction in the overhead of using these popular language features, thereby speeding up production scripts.

Another improvement is the revised Test-Connection, a command you use to test a network connection with a remote system. Test-Connection, in PowerShell 7, not only does more, but is faster than with Windows PowerShell.

Error reporting in Windows PowerShell was excellent: clear and generally actionable error messages with details of exactly where the error occurred. In PowerShell 7, you now get, by default, a concise view of an error without all the extra text that was often of little value. As always, you can revert to less concise messages if you choose. In the Exploring the error view and Get-Error recipe, you see how error reporting (in Windows PowerShell) becomes better with PowerShell 7.

In the final recipe of the chapter, we take a look at some of the experimental features that can be enabled in PowerShell 7.

 

Exploring new operators

Operators are symbols or combinations of keystrokes that PowerShell recognizes and assigns some meaning to. PowerShell uses the + operator to mean addition, either arithmetic addition or string addition/concatenation. Most of the PowerShell operators were defined with Windows PowerShell V1.

PowerShell 7 now implements some new operators, including the following:

  • Pipeline chain operators: || and &&
  • Null-coalescing operator: ??
  • Null-coalescing assignment operator: ??=
  • Experimental null conditional member access operators: ?. and ?[]
  • Background processing operator: &
  • Ternary operator: ? <if-true> : <if-false>

You see examples of these operators in this recipe.

Getting ready

This recipe uses SRV1, a Windows Server 2020 host. You have installed and configured PowerShell 7 and VS Code. You run this, and all remaining recipes in this book, in either a PowerShell 7 console or VS Code.

How to do it...

  1. Using PowerShell 7 to check results traditionally
    Write-Output 'Something that succeeds'
    if ($?) {Write-Output 'It worked'}
    
  2. Checking results with the pipeline operator &&
    Write-Output 'Something that succeeds' && Write-Output 'It worked'
    
  3. Using the pipeline chain operator ||
    Write-Output 'Something that succeeds' || 
      Write-Output 'You do not see this message'
    
  4. Defining a simple function
    function Install-CascadiaPLFont{
      Write-Host 'Installing Cascadia PL font...'
    }
    
  5. Using the || operator
    $OldErrorAction        = $ErrorActionPreference
    $ErrorActionPreference = 'SilentlyContinue'
    Get-ChildItem -Path C:\FOO\CASCADIAPL.TTF || 
       Install-CascadiaPLFont
    $ErrorActionPreference = $OldErrorAction
    
  6. Creating a function to test null handling
    Function Test-NCO {
      if ($args -eq '42') {
        Return 'Test-NCO returned a result'
      }
    }
    
  7. Testing null results traditionally
    $Result1 = Test-NCO    # no parameter
    if ($null -eq $Result1) {
        'Function returned no value'
    } else {
        $Result1
    }
    $Result2 = Test-NCO 42  # using a parameter
    if ($null -eq $Result2) { 
        'Function returned no value'
    } else {
        $Result2
    }
    
  8. Testing using the null-coalescing operator ??
    $Result3 =  Test-NCO
    $Result3 ?? 'Function returned no value'
    $Result4 =  Test-NCO 42
    $Result4 ?? 'This is not output, but result is'
    
  9. Demonstrating the null conditional assignment operator
    $Result5 = Test-NCO
    $Result5 ?? 'Result is null'
    $Result5 ??= Test-NCO 42
    $Result5
    
  10. Running a method on a null object traditionally
    $BitService.Stop()
    
  11. Using the null conditional operator for a method
    ${BitService}?.Stop()
    
  12. Testing null property name access
    $x = $null
    ${x}?.Propname
    $x = @{Propname=42}
    ${x}?.Propname
    
  13. Testing array member access of a null object
    $y = $null
    ${y}?[0]
    $y = 1,2,3
    ${y}?[0]
    
  14. Using the background processing operator &
    Get-CimClass -ClassName Win32_Bios &
    
  15. Waiting for the job to complete
    $JobId = (Get-Job | Select-Object -Last 1).Id
    Wait-Job -id $JobId
    
  16. Viewing the output
    $Results = Receive-Job -Id $JobId
    $Results | Format-Table
    
  17. Creating an object without using the ternary operator
    $A = 42; $B = (42,4242) | Get-Random
    $RandomTest = ($true, $false) | Get-Random
    if ($A -eq $B) {
      $Property1 = $true
    } else {
      $Property1 = $false
    }
    if ($RandomTest) {
      $Property2 = "Hello"
    } else {
      $Property2 = "Goodbye"
    }
    [PSCustomObject]@{
      "Property1" = $Property1
      "Property2" = $Property2
    }
    
  18. Creating an object using the ternary operator
    [PSCustomObject]@{
        "Property1" = (($A -eq $B) ? $true : $false)
        "Property2" = (($RandomTest) ? "Hello" : "Goodbye")    
    }
    

How it works...

In step 1, you write output, which succeeds. Then you test the value of $? to determine whether that previous step did, in fact, succeed. The output is as follows:

Figure 2.1: Checking results traditionally

In step 2, you use the && operator to check that a preceding command finished without an error. The output looks like this:

Figure 2.2: Checking results with the pipeline operator

The pipeline chain operator, ||, tells PowerShell to run the commands after the operator if the preceding command fails (in effect, the opposite to &&). In step 3, you see the operator in use, with output like this:

Figure 2.3: Using the pipeline chain operator

In step 4, you define a function. Defining the function produces no output. This function writes output to simulate the installation of the Cascadia Code PL font.

In step 5, you check to see whether the TTF file exists, and if not, you call the Install-CascadiaPLFont function to simulate installing the font. By piping the output from Get-ChildItem to Out-Null, you avoid the actual output from Get-ChildItem, and if the file does not exist, you call the Install-CascadiaPLFont function. The output of this snippet looks like this:

Figure 2.4: Using the || operator and installing the Cascadia font

To illustrate the handling of null results from a function, in step 6, you create a function that either returns nothing (if you call the function with no parameters) or a string value (if you call it specifying a parameter). This function illustrates how you can handle a function that returns null. This step produces no output.

In step 7, you illustrate the traditional handling of a function that returns null. You call the function, first without a parameter, that returns no result and then with a value that does return a value. You then test to see whether the function returned an actual value in each case, which looks like this:

Figure 2.5: Testing null results traditionally

When you use the null-coalescing operator (??) between two operands, the operator returns the value of its left-hand operand if it isn't null; otherwise, it evaluates the right-hand operand and returns the results. In step 8, you call the Test-NCO function and check whether the function returns a value, which looks like this:

Figure 2.6: Testing using the null-coalescing operator

You use the null conditional assignment operator, ??=, to assign a value to a variable if that variable is currently null, as you can see in step 9, the output from which looks like this:

Figure 2.7: Using the null conditional assignment operator

One common issue often seen in the various PowerShell support forums arises when you attempt to invoke a method on an object that is null. You might have used an expression or a command to attempt to return a value (for example, all AD users in the Marin County office) and that returns a null values. In step 10, you attempt to invoke the Stop() method on the $BitService object. Since you have not assigned a value to $BitService, you see the result (an error, You cannot call a method on a null-valued expression). The traditional method of displaying errors looks like this:

Figure 2.8: Running a method on a null object traditionally

By using the null conditional operator, you can run the Stop() method if the $BitService variable is non-null, but skip calling the method if the variable is null. In effect, what you are doing in step 11 is calling the Stop() method if the variable is non-null, and doing nothing otherwise. Because the variable does not have a value, this step does nothing (and produces no output).

When a variable is null, whether due to an error in your scripts or because a command returns a null instead of an actual value, accessing property names can also cause errors. The output of step 12 looks like this:

Figure 2.9: Testing null property name access

You can also encounter issues with null objects when you attempt to access an array member of an object that may or may not exist. In step 13, you attempt to access an array member of an array that does not exist, followed by one that does exist. The output from this step looks like this:

Figure 2.10: Testing array member access of a null object

In step 14, you investigate the use of the background processing operator, &. The idea is that you append this character to the end of a command or script, and PowerShell runs that code in the background. The output from this step looks like this:

Figure 2.11: Using the background processing operator

In step 15, you wait for the job you created in step 14 to complete, which looks like this:

Figure 2.12: Waiting for the job to complete

After the job has completed, in step 16, you receive and display the job's output, which looks like this:

Figure 2.13: Displaying the job's output

In step 17, you create an object using a more traditional approach. This step creates a random value for two properties. Then, you create an object using the values of these two properties. The output from this step looks like this:

Figure 2.14: Creating an object without using the ternary operator

In step 18, you use the new PowerShell 7 ternary operator. This operator tests a condition and runs different code depending on whether the result is true or false. This is very similar to what you saw in step 17, but in a lot fewer lines of code. The output of this step looks like this:

Figure 2.15: Creating an object using the ternary operator

There's more...

In step 4, the function you create simulates the installation of a font if the font does not exist. The font file, CASCADIAPL.TTF, is a TrueType font file for the Cascadia Code Powerline font. This font is the Cascadia Code font you installed in Chapter 1, Installing and Configuring PowerShell 7.1, with the addition of symbols for Powerline. For more details on this font, see https://www.hanselman.com/blog/PatchingTheNewCascadiaCodeToIncludePowerlineGlyphsAndOtherNerdFontsForTheWindowsTerminal.aspx.

In step 5, you simulate the installation of the font if it does not already exist. When you check to test whether the TTF file currently exists, the default setting for $ErrorActionPreference (Continue) means you see an error message if the file does not exist. By default, when Get-ChildItem checks to see whether the file exists, it generates an error message if the file does not exist. One approach to avoiding this error message is to set the value of $ErrorActionPreference to SilentlyContinue and, after ensuring that the font file now exists, set it back to the default value. The syntax is a bit convoluted unless you are familiar with it; this may be another case where not using these operators, and using the Windows PowerShell approach instead, might make the script easier to read and understand.

In steps 11 and 12, you attempt to access a property from an object. The assumption here is that you only want to invoke the method or access a property value if the object exists and you do not care otherwise. Thus, if $BitService has a value, you call the Stop() method, otherwise the code carries on without the script generating errors. This approach is great from the command line, but in production scripts, the approach could mask other underlying issues. As with all PowerShell features, you have to use null handling with due care and attention.

With step 14, you tell PowerShell to run a command as a background job by appending the & character to the command. Using this operator is a more straightforward way to invoke the command as a job than by calling Invoke-Command and specifying the command using the -ScriptBlock or -Script parameters.

In steps 15 and 16, you use Get-Job, Wait-Job, and Receive-Job to wait for the last job run and get the output. One downside to not using Start-Job to create a background job is that you cannot specify a job name. That means using the technique shown in step 15 to obtain the job and job results for the job created in step 14. Using that technique is thus more useful from the command line than in a production script.

In step 17, you create an object using older Windows PowerShell syntax, whereas, in step 18, you use the ternary operator. As with other operators, use this with care, and if you use these operators in production code, make sure you document what you are doing.

In this recipe, you have seen the new operators added to PowerShell 7. Most of them provide a shortcut way to perform some operation or other, particularly at the command line.

 

Exploring parallel processing with ForEach-Object

Situations often arise where you want to run many commands in parallel. For example, you might have a list of computer names, and for each of those computers, you want to run a script on that computer. You might wish to verify the status and resource usage of various services on each computer. In this scenario, you might use Get-Content to get an array of computer names, and then use either ForEach or ForEach-Object to run the script on the computer. If there are 10 computers and the script takes 10 minutes, the total runtime is over 100 minutes.

With Windows PowerShell, the only built-in methods of running scripts in parallel were using background jobs or using workflows. With background jobs, you could create a set of jobs, each of which starts a script on a single computer. In that case, PowerShell runs each job in a separate process, which provides isolation between each job but is resource-intensive. The Windows PowerShell team added workflows with Windows PowerShell V4, which also allow you to run script blocks in parallel. However, workflows are not carried forward into PowerShell 7. Like other features no longer available in PowerShell 7, you can continue to use Windows PowerShell to run workflows and gradually convert them as and when appropriate.

An alternative to background jobs is to use the ThreadJob module you can download from the PowerShell gallery. For more details on this module, see its repository page at https://github.com/PaulHigin/PSThreadJob.

With PowerShell 7, the PowerShell team added an option to the ForEach-Object command to allow you to run script blocks in parallel. This option simplifies running script blocks or scripts, especially long-running ones, in parallel and avoids the need for third-party modules or having to deal with the complexity of workflows.

This recipe demonstrates running operations in parallel traditionally, using background jobs, and using ForEach-Object -Parallel.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7 and, optionally, VS Code.

How to do it...

  1. Simulating a long-running script block
    $SB1 = {
      1..3 | ForEach-Object {
        "In iteration $_"
        Start-Sleep -Seconds 5
      } 
    }
    Invoke-Command -ScriptBlock $SB1
    
  2. Timing the expression
    Measure-Command -Expression $SB1
    
  3. Refactoring into code that uses jobs
    $SB2 = {
    1..3 | ForEach-Object {
      Start-Job -ScriptBlock {param($X) "Iteration $X " ;
                              Start-Sleep -Seconds 5} -ArgumentList $_ 
    }
    Get-Job | Wait-Job | Receive-Job -Keep
    }
    
  4. Invoking the script block
    Invoke-Command -ScriptBlock $SB2
    
  5. Removing any old jobs and timing the script block
    Get-Job | Remove-Job
    Measure-Command -Expression $SB2
    
  6. Defining a script block using ForEach-Object -Parallel
    $SB3 = {
    1..3 | ForEach-Object -Parallel {
                   "In iteration $_"
                   Start-Sleep -Seconds 5
             } 
    }
    
  7. Executing the script block
    Invoke-Command -ScriptBlock $SB3
    
  8. Measuring the script block execution time
    Measure-Command -Expression $SB3
    
  9. Creating and running two short script blocks
    $SB4 = {
        1..3 | ForEach-Object {
                       "In iteration $_"
                 } 
    }
    Invoke-Command -ScriptBlock $SB4    
    $SB5 = {
            1..3 | ForEach-Object -Parallel {
                           "In iteration $_"
                 } 
    }
    Invoke-Command -ScriptBlock $SB5
    
  10. Measuring the execution time for both script blocks
    Measure-Command -Expression $SB4
    Measure-Command -Expression $SB5
    

How it works...

In step 1, you create and then invoke a script block. The script block simulates how you can run several long script blocks traditionally using the ForEach-Object cmdlet, with output like this:

Figure 2.16: Simulating a long-running script block

In step 2, you determine how long it takes PowerShell to run this script block, with output like this:

Figure 2.17: Timing the expression

In step 3, you refactor the $SB1 script block to use PowerShell background jobs. The script block runs the simulated long-running task using jobs and then waits for and displays the output from each job. The concept is that instead of doing each iteration serially, all the jobs run in parallel. Defining the function creates no output.

In step 4, you invoke the script block to view the results, which looks like this:

Figure 2.18: Invoking the script block

In step 5, you remove any existing jobs and then re-run the updated script block. This step enables you to determine the runtime for the entire expression. The output of this step looks like this:

Figure 2.19: Removing any existing jobs and timing the script block

In step 6, you create another script block that uses the PowerShell 7 ForEach-Object -Parallel construct. When you define this script block, PowerShell creates no output.

In step 7, you run the script block, which looks like this:

Figure 2.20: Executing the script block created in step 6

In step 8, you time the execution of the script block, making use of the ForEach-Object -Parallel feature, which looks like this:

Figure 2.21: Timing the script block execution

In step 9, you define and then invoke two script blocks, which looks like this:

Figure 2.22: Creating and running two short script blocks

In the final step in this recipe, step 10, you measure the execution time of these two script blocks, which looks like this:

Figure 2.23: Measuring execution time of the script blocks created in step 9

There's more...

In steps 1 and 2, you invoke a long-running task multiple times. As you can see from Figure 2.17, running these script blocks, one at a time, takes just over 15 seconds. In step 5, you see that by refactoring the long-running task into PowerShell background jobs, you reduce the runtime to 6.83 seconds. Finally, in step 8, you measure the elapsed runtime when you use ForEach-Object -Parallel, which is now a little over 5 seconds.

As this recipe shows, if you have independent script blocks, you can run them in parallel to reduce the overall runtime, in this case, from just over 15 seconds to just over 5. And the gains would have been even higher had you run the loop more than three times. Running the loop serially 10 times would have taken over 50 seconds, compared to just over 5 for ForEach-Object -Parallel.

However, there is a default limit of five script blocks that PowerShell can run simultaneously. You can use the -ThrottleLimit parameter to allow more or less than that default. One thing to note: if you attempt to run more parallel script blocks than you have processor cores, PowerShell just uses a processor core queue. This all takes time and would end up raising the overall runtime. The good news is that PowerShell handles all this, so if you run, say, 1,000 parallel script blocks on a system with 12 processor cores, PowerShell works as fast as your host computer allows.

It is also worth remembering that there is some overhead involved in ForEach-Object -Parallel. Under the hood, the command has to set up and then manage separate threads of execution. If the script block is very short, you can find that the overhead involved results in slower runtimes. In this case, the runtime went from 2.9 ms to 83.7 ms. The critical point here is that this construct is useful for non-trivial script blocks (or scripts) that you run in parallel. You benefit up to the number of cores you have available.

Another thing to note is that when you use the ForEach-Object {script} syntax, you are using a positional parameter (-Process). On the other hand, when you use the -Parallel parameter, the parameter value is the script block you wish PowerShell to run in parallel.

 

Improvements in ForEach and ForEach-Object

Windows PowerShell users are well versed in the use of both the ForEach statement and the ForEach-Object cmdlet. You can use both of these methods in your scripts to process collections, such as all the users in a specific Active Directory group, or the audio files in a file share. In PowerShell 7, both of these iteration methods are considerably faster.

Using either ForEach mechanism is a quick and easy way of processing a collection. One downside some IT pros may have noticed is that the overhead of ForEach processing in Windows PowerShell grows with the size of the collection. With small collection sizes, you are not likely to notice any difference. As the collection size grows, so does the overhead.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7 and have created a console profile file. Run this recipe in an elevated console.

How to do it...

  1. Creating a remoting connection to the localhost using Windows PowerShell
    New-PSSession -UseWindowsPowerShell -Name 'WPS'
    
  2. Getting a remoting session
    $Session = Get-PSSession -Name 'WPS'
    
  3. Checking the version of PowerShell in the remoting session
    Invoke-Command -Session $Session  -ScriptBlock {$PSVersionTable}
    
  4. Defining a long-running script block using ForEach-Object
    $SB1 = {
      $Array  = (1..10000000)
      (Measure-Command {
        $Array | ForEach-Object {$_}}).TotalSeconds
    }
    
  5. Running the script block locally:
    [gc]::Collect()
    $TimeInP7 = Invoke-Command -ScriptBlock $SB1 
    "Foreach-Object in PowerShell 7.1: [{0:n4}] seconds" -f $TimeInP7
    
  6. Running the script block in PowerShell 5.1
    [gc]::Collect()
    $TimeInWP  = Invoke-Command -ScriptBlock $SB1 -Session $Session
    "ForEach-Object in Windows PowerShell 5.1: [{0:n4}] seconds" -f $TimeInWP
    
  7. Defining another long-running script block using ForEach
    $SB2 = {
        $Array  = (1..10000000)
        (Measure-Command {
          ForEach ($Member in $Array) {$Member}}).TotalSeconds
    }
    
  8. Running it locally in PowerShell 7
    [gc]::Collect()
    $TimeInP72 = Invoke-Command -ScriptBlock $SB2 
    "Foreach in PowerShell 7.1: [{0:n4}] seconds" -f $TimeInP72
    
  9. Running it in Windows PowerShell 5.1
    [gc]::Collect()
    $TimeInWP2  = Invoke-Command -ScriptBlock $SB2 -Session $Session
    "Foreach in Windows PowerShell 5.1: [{0:n4}] seconds" -f $TimeInWP2
    

How it works…

In step 1, you use New-PSSession to create a remoting session using a Windows PowerShell endpoint. This step produces output like this:

Figure 2.24: Creating a remoting connection to the localhost

In step 2, you get the session object representing the session you created in the previous step. This creates no output.

In step 3, you obtain the version of PowerShell that the remoting session is using to process commands, namely, Windows PowerShell 5.1. The output of this step looks like this:

Figure 2.25: Checking the PowerShell version in the remoting session

In step 4, you create a script block, $SB1, which uses the ForEach-Object cmdlet to iterate over a large collection. This step creates no output.

You invoke the $SB1 script block in the local session, in step 5. This step runs the script block in PowerShell 7. The output from this step looks like this:

Figure 2.26: Running the script block locally

With step 6, you run the $SB1 script block in Windows PowerShell 5.1, which produces output like this:

Figure 2.27: Running the script block in PowerShell 5.1

You next create a script block that makes use of the ForEach syntax item, in step 7, producing no output. You then run this second script block in PowerShell 7, in step 8, which produces output like this:

Figure 2.28: Running the script block locally in PowerShell 7

In the final step, step 9, you run $SB1 in the remoting session created earlier (in other words, in Windows PowerShell 5.1), which produces output like this:

Figure 2.29: Running the script block in PowerShell 5.1

There's more...

In step 1, you create, implicitly, a remoting session to the localhost using a process transport that is much faster than the traditional remoting session using WinRM.

In steps 5, 6, 8, and 9, you force .NET to perform a garbage collection. These are steps you can use to minimize the performance hits of running a script block in a remote session (in Windows PowerShell) and to reduce any impact of garbage collections while you are performing the tests in this recipe.

As you can see from the outputs, running ForEach-Object is much faster in PowerShell 7, as is running ForEach in PowerShell 7. Processing large collections of objects is a lot faster in PowerShell 7.

The improvements to loop processing that you can see in the recipe, combined with the use of ForEach-Object -Parallel you saw in Exploring parallel processing with ForEach-Object, provide an excellent reason to switch to PowerShell 7 for most operations.

The performance of iterating through large collections is complex. You can read an excellent article on this subject at https://powershell.one/tricks/performance/pipeline. This article also addresses the performance of using the pipeline versus using ForEach to iterate across collections in a lot more detail.

 

Improvements in Test-Connection

In Windows PowerShell, you could use the Test-Connection cmdlet as a replacement for the Win32 console command, ping.exe. One advantage of using the cmdlet was that the cmdlet returns objects that you can use more easily in scripting. You can use string hacking and regular expressions to extract the same data from the output of ping.exe, but that is a lot more work and results in scripts that are harder to read.

With Windows PowerShell 5.1, the Test-Connection command makes use of WMI. The command returns objects of the type System.Management.ManagementObject#root\cimv2\Win32_PingStatus. With PowerShell 7.1, the command no longer depends on WMI and returns objects of the type Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus. As a result of this change of object type, property names returned in PowerShell 7.1 differ from the properties returned in Windows PowerShell. Scripts that made use of some properties may not work properly without adjustment, in PowerShell 7, but that should not be a common issue.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7 and created a console profile file.

How to do it...

  1. Using Test-Connection with the -Target parameter
    Test-Connection -TargetName www.packt.com -Count 1
    
  2. Using Test-Connection with an IPv4 address
    Test-Connection -TargetName www.packt.com -Count 1 -IPv4
    
  3. Using Resolve-DnsName to resolve the destination address
    $IPs = (Resolve-DnsName -Name Dns.Google -Type A).IPAddress
    $IPs | 
      Test-Connection -Count 1 -ResolveDestination
    
  4. Resolving the destination and performing a traceroute
    Test-Connection -TargetName 8.8.8.8 -ResolveDestination -Traceroute |
      Where-Object Ping -eq 1
    
  5. Using infinite ping and stopping with Ctrl-C
    Test-Connection -TargetName www.reskit.net -Repeat
    
  6. Checking the speed of Test-Connection in PowerShell 7
    Measure-Command -Expression {test-connection 8.8.8.8 -count 1}
    
  7. Checking the speed of Test-Connection in Windows PowerShell
    $Session = New-PSSession -UseWindowsPowerShell
    Invoke-Command -Session $Session -Scriptblock {
        Measure-Command -Expression {
          Test-Connection -ComputerName 8.8.8.8 -Count 1}
    }
    

How it works…

In step 1, you test the connection between SRV1 and our publisher's online website. The output of this command looks like this:

Figure 2.30: Using Test-Connection with the -TargetName parameter

If you have a computer with a working IPv6 address, Test-Connection prefers using IPv6, by default, as shown in the output from step 1. Should you want to test the IPv4 connection specifically, you can specify the -IPv4 switch explicitly, as shown in step 2:

Figure 2.31: Using Test-Connection with an IPv4 address

In step 3, you use Resolve-DnsName cmdlet to determine the IPv4 address(es) for Dns.Google. This site is Google's free DNS service, which Google offers via two well-known IPv4 addresses (8.8.8.8 and 8.8.4.4), which you can see in the output from this step:

Figure 2.32: Using Resolve-DnsName to resolve the destination address

The tracert.exe Win32 console application allows you to trace the route between your host system and some external host. In step 4, you use Test-Connection to trace the route between SRV1 and the computer at 8.8.8.8. The output of this step looks like this:

Figure 2.33: Resolving the destination and trace route

With the ping.exe Win32 console application, you can specify the -t parameter to ping the target host continuously, which you can then stop by entering Ctrl-C. With Test-Connection in PowerShell 7, you can now use the -Repeat parameter to achieve the same outcome (and stop the test using Ctrl-C). You can see this in the output of step 5:

Figure 2.34: Using infinite ping and stopping with Ctrl-C

In steps 6 and 7, you compare the speed of Test-Connection in Windows PowerShell and PowerShell 7. Running Test-Connection in PowerShell 7 looks like this:

Figure 2.35: Checking the speed of Test-Connection in PowerShell 7

In Windows PowerShell 5.1, the output is similar (although slower) and looks like this:

Figure 2.36: Checking the speed of Test-Connection in Windows PowerShell 5.1

There's more...

In the output from step 1, you can see that the results are formatted differently from Windows PowerShell, with improvements to the output.

Step 2 shows how, in PowerShell 7, you can use the -IPv4 switch to use IPv4 explicitly. Similarly, if you want to use IPv6 specifically, you can use the -IPv6 switch. Neither switch was available with Windows PowerShell.

In step 3, you determine the IP addresses for the host Dns.Google (8.8.8.8 and 8.8.4.4), which you then ping successfully. Note that this step both resolves the IP addresses into hostnames and performs the pings against each IP address. Google runs a free DNS service available to anyone on the internet. You can find out more at https://developers.google.com/speed/public-dns.

In steps 6 and 7, you compare the speed of the Test-Connection command between Windows PowerShell 5.1 and PowerShell 7.1, As you can see in the output from these steps, the command is considerably faster in PowerShell 7.

 

Using Select-String

The Select-String command, included with Windows PowerShell, has been improved in PowerShell 7. You use this command to search for strings either inside pipelined objects or within text files. This command is conceptually similar to the grep command in Linux. With PowerShell 7, the PowerShell team has added some excellent new features to this excellent command, which you look at in this recipe.

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7 and Visual Studio Code, and once you have created a console profile file.

How to do it...

  1. Getting a file of text to work with
    $Source       = 'https://www.gutenberg.org/files/1661/1661-0.txt'
    $Destination  = 'C:\Foo\Sherlock.txt'
    Start-BitsTransfer -Source $Source -Destination $Destination
    
  2. Getting the book's contents
    $Contents = Get-Content -Path $Destination
    
  3. Checking the length of The Adventures of Sherlock Holmes
    "The book is {0} lines long" -f $Contents.Length
    
  4. Searching for "Watson" in the book's contents
    $Match1 = $Contents | Select-String -Pattern 'Watson'
    "Watson is found {0} times" -f $Match1.Count
    
  5. Viewing the first few matches
    $Match1 | Select-Object -First 5
    
  6. Searching for "Dr. Watson" with a regular expression
    $Contents | Select-String -Pattern 'Dr\. Watson'
    
  7. Searching for "Dr. Watson" using a simple match
    $Contents | Select-String -Pattern 'Dr. Watson' -SimpleMatch
    
  8. Viewing the output when searching from files
    Get-ChildItem -Path $Destination |
      Select-String -Pattern 'Dr\. Watson'
    

How it works…

In this recipe, you look at how you can use the Select-String cmdlet included with PowerShell 7. To investigate this cmdlet, you first download a text file.

In step 1, you use the Start-BitsTransfer command to download the text for a book, The Adventures of Sherlock Holmes, by Sir Arthur Conan Doyle, from the Project Gutenberg website. This step produces no output.

In step 2, you get the text from this book, which you store in the $Contents variable. This step produces no output. In step 3, you report the length of the book, which looks like this:

Figure 2.37: Checking the length of the book

In step 4, you search the book's contents to find all occurrences of "Watson", Sherlock Holmes' faithful companion. This results in 81 occurrences, and looks like this:

Figure 2.38: Searching for occurrences of "Watson" in the book

In step 5, you use the Select-Object cmdlet to view the first five times the command finds the term "Watson" in the book's contents, which looks like this:

Figure 2.39: Viewing the first five "Watson" matches in the book

With Select-String, you can specify a regular expression with which to match the contents. In step 6, you specify a regular expression pattern to search for the string "Dr. Watson". The output of this step looks like this:

Figure 2.40: Searching for "Dr. Watson" with a regular expression pattern

As an alternative to using a regular expression to perform searching, Select-String also takes a simple match, as shown in the output of step 7:

Figure 2.41: Searching for "Dr. Watson" using a simple match

In the previous steps, you have used Select-String to search for the contents of a variable. Another valuable feature of Select-String is the ability to search for text in a file or even multiple files. You can see this in step 8, the output of which looks like this:

Figure 2.42: Searching for "Dr. Watson" in the Sherlock.txt file

There's more...

In steps 1 and 2, you download a text file from Project Gutenberg, a free internet library of eBooks. This site contains a large number of free books in a variety of formats, including basic text. To find more free eBooks, visit the home page at https://www.gutenberg.org/, and to read more about the project, see https://www.gutenberg.org/about/.

An essential improvement to Search-String in PowerShell 7 is the highlighting of the selected string, as you can see in the outputs of steps 5, 6, 7, and 8. From the command line, this makes viewing the output from Select-String much easier to consume. Also, the ability to search across multiple files, as shown in step 8, makes the Select-String cmdlet even more useful.

 

Exploring the error view and Get-Error

Since the very beginning, Windows PowerShell has done a great job in displaying the results of errors: a big blob of red text on a black background that contains full details about what went wrong. It was tremendous, but many new users found it a bit off-putting – there was too much information, some of which was not very useful in most cases.

PowerShell 7 now offers a more concise view of errors that reduces the amount of text and improves the format of the output. The result is shorter and more readable output. And, on those rare occasions when it might be necessary, you can Get-Error to get complete error details without having to parse through $Error[0].

Getting ready

You run this recipe on SRV1 after you have installed PowerShell 7 and/or Visual Studio Code, and once you have created a console profile file.

How to do it...

  1. Creating a simple script
    $SCRIPT = @'
      # divide by zero
      42/0  
    '@
    $SCRIPTFILENAME = 'C:\Foo\ZeroDivError.ps1'
    $SCRIPT | Out-File -Path $SCRIPTFILENAME
    
  2. Running the script and seeing the default error view
    & $SCRIPTFILENAME
    
  3. Running the same line from the console
    42/0
    
  4. Viewing the $ErrorView variable
    $ErrorView
    
  5. Viewing the potential values of $ErrorView
    $Type = $ErrorView.GetType().FullName
    [System.Enum]::GetNames($Type)
    
  6. Setting $ErrorView to 'NormalView' and recreating the error
    $ErrorView = 'NormalView'
    & $SCRIPTFILENAME
    
  7. Setting $ErrorView to 'CategoryView' and recreating the error
    $ErrorView = 'CategoryView'
    & $SCRIPTFILENAME
    
  8. Setting $ErrorView to its default value
    $ErrorView = 'ConciseView'
    

How it works...

In step 1, you create a script that contains a (deliberate) divide-by-zero error. This step creates the file, but creates no other output.

In step 2, you run the script from within VS Code, and view the resulting error, which looks like this:

Figure 2.43: Running the script and viewing the error

In step 3, you create a divide-by-zero error from the command line. The output from this step looks like this:

Figure 2.44: Running the same line from the console

PowerShell 7 uses the built-in $ErrorView variable to hold the name of the error view PowerShell should use to display errors. In step 4, you view the current value of this variable, which looks like this:

Figure 2.45: Viewing the value of the $ErrorView variable

The $ErrorView variable can take one of three values, as you can see from the output of step 5:

Figure 2.46: Viewing the potential values of $ErrorView

In step 6, you set the value of $ErrorView to display the error using the output generated by Windows PowerShell and then re-view the error, which looks like this:

Figure 2.47: Setting $ErrorView to NormalView and recreating the error

In step 7, you set $ErrorView to display the error using CategoryView and then recreate the error. The output from this step shows the category error view:

Figure 2.48: Setting $ErrorView to CategoryView and recreating the error

In step 8, you reset the value of $ErrorView to the default value. This step creates no output.

There's more...

The concise error view you see in the output from step 2 contains all the information from the standard view that you can see in the output from step 7, except for the omission of the error category information. And if you invoke the error directly from the command line, as shown in step 3, you see only the error message, which is easier on the eyes.

In step 5, you view the error category information. In most cases, this is not particularly useful.

In step 8, you reset the value of $ErrorView. Depending on what you are doing, this step may not be needed. You can just exit the PowerShell console (or VS Code), and the next time you start PowerShell, it resets the value back to the default (ConciseView). And if you should prefer the normal or category error views, you can always set a value to $ErrorView in your profile file.

Although not shown in this recipe, you can use the Get-Error cmdlet to show you complete error information about a specific error. For most IT Professionals, the basic error information provided by PowerShell 7 is more than adequate (and a great improvement over error output with Windows PowerShell).

 

Exploring experimental features

During the development of PowerShell Core and later with PowerShell 7, the PowerShell team have routinely added new features. Some of these new features could, at least in theory, break existing scripts and are called "experimental." PowerShell does not, by default, enable any of these features. As shown in this recipe, you must enable them explicitly. This approach to experimental features enables you to test these new features and provide the PowerShell team with feedback. Should you find a feature that breaks a script for you, disable it. If you turn on (or turn off) an experimental feature, you need to restart PowerShell 7.

In general, experimental features are not intended to be used in production since the experimental features, by design, can be breaking. Also, experimental features are not officially supported. That being said, so far, these features have been very stable and reliable.

In this recipe, you look at the experimental features available in PowerShell 7.1 as released. If you are using later versions (for example, a PowerShell 7.2 preview release), you may see different experimental features. For a fuller look at PowerShell's experimental features, see https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_experimental_features.

Getting ready

You run this recipe on SRV1 after you install PowerShell 7 and/or Visual Studio Code, and once you have created a console profile file.

How to do it...

  1. Discovering the experimental features
    Get-ExperimentalFeature -Name * |
      Format-Table Name, Enabled, Description -Wrap
    
  2. Examining the "command not found" result with no experimental features available
    Foo
    
  3. Enabling one experimental feature as the current user
    Get-ExperimentalFeature -Name * | 
      Select-Object -First 1 |
        Enable-ExperimentalFeature -Scope CurrentUser -Verbose
    
  4. Enabling one experimental feature for all users
    Get-ExperimentalFeature -Name * | 
      Select-Object -Skip 1 -First 1 |
        Enable-ExperimentalFeature -Scope AllUsers -Verbose
    
  5. Starting a new PowerShell console

    If you are using VS Code to run this recipe, enter Ctrl + Shift + ` to start a new terminal. If you are using the PowerShell 7 console, start a new copy of the console.

  6. Examining the experimental features
    Get-ExperimentalFeature
    
  7. Examining output from the "command not found" suggestion feature
    Foo
    

How it works...

In step 1, you use the Get-ExperimentalFeature cmdlet to discover the available experimental features and their current state, which (by default) looks like this:

Figure 2.49: Discovering experimental features

To test out an experimental feature, in step 2, you run a non-existent command, with output such as this:

Figure 2.50: Examining the "command not found" result

In step 3, you enable the first feature, the PSCommandNotFoundSuggestion experimental feature for the current user, which looks like this:

Figure 2.51: Enabling an experimental feature for the current user

In step 4, you enable the second experimental feature, PSCultureInvariantReplaceOperator, which looks like this:

Figure 2.52: Enabling an experimental feature for all users

In step 5, you start a new version of PowerShell. This step produces no output as such.

In step 6, you examine the state of experimental features, noting that two new features are now available, which looks like this:

Figure 2.53: Examining experimental features

In step 7, you re-run the unknown command to observe the "command not found" suggestions, which look like this:

Figure 2.54: Examining the output from the "command not found" suggestion feature

There's more...

In this recipe, you turn on two experimental features and examine one ("command not found" suggestions). In most cases, you should be safe to enable all of the experimental features, but it is always safer to turn them on one by one and test your scripts carefully.

About the Author
  • Thomas Lee

    Thomas Lee is a consultant/trainer/writer based in the UK and has been in the IT business since the late 1960s. After graduating from Carnegie Mellon University, Thomas joined ComShare where he was a systems programmer building the Commander II time-sharing operating system, a forerunner of today's cloud computing paradigm. In the mid-1970s, he moved to ICL to work on the VME/K operating system. After a sabbatical in 1980/81, he joined Accenture, leaving in 1988 to run his own consulting and training business, which is still active today. Thomas holds numerous Microsoft certifications, including MCSE (one of the first in the world) and later versions, MCT (25 years), and was awarded Microsoft's MVP award 17 times.

    Browse publications by this author